From 8e485c2dc21b9af1270db6d2e4a6f85da2678a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 10 Dec 2020 13:44:34 +0000 Subject: [PATCH 01/41] prevent sharing with increased permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../services/gateway/usershareprovider.go | 2 + internal/http/services/owncloud/ocdav/copy.go | 6 +- .../http/services/owncloud/ocdav/delete.go | 2 +- .../http/services/owncloud/ocdav/error.go | 4 +- internal/http/services/owncloud/ocdav/get.go | 4 +- internal/http/services/owncloud/ocdav/head.go | 2 +- .../http/services/owncloud/ocdav/mkcol.go | 4 +- internal/http/services/owncloud/ocdav/move.go | 12 +- .../http/services/owncloud/ocdav/propfind.go | 6 +- .../http/services/owncloud/ocdav/proppatch.go | 6 +- .../services/owncloud/ocdav/publicfile.go | 4 +- internal/http/services/owncloud/ocdav/put.go | 6 +- .../http/services/owncloud/ocdav/trashbin.go | 14 +- internal/http/services/owncloud/ocdav/tus.go | 6 +- .../http/services/owncloud/ocdav/versions.go | 6 +- .../owncloud/ocs/conversions/permissions.go | 63 ++++++- .../ocs/conversions/permissions_test.go | 6 +- .../handlers/apps/sharing/shares/public.go | 44 +---- .../handlers/apps/sharing/shares/remote.go | 41 +--- .../handlers/apps/sharing/shares/shares.go | 178 ++++++------------ .../ocs/handlers/apps/sharing/shares/user.go | 126 +++++++++++++ pkg/storage/fs/ocis/node.go | 26 ++- pkg/storage/utils/eosfs/eosfs.go | 55 +++++- 23 files changed, 373 insertions(+), 250 deletions(-) diff --git a/internal/grpc/services/gateway/usershareprovider.go b/internal/grpc/services/gateway/usershareprovider.go index 35ef3960b2..5d5e34a479 100644 --- a/internal/grpc/services/gateway/usershareprovider.go +++ b/internal/grpc/services/gateway/usershareprovider.go @@ -46,6 +46,8 @@ func (s *svc) CreateShare(ctx context.Context, req *collaboration.CreateShareReq Status: status.NewInternal(ctx, err, "error getting user share provider client"), }, nil } + // TODO the user share manager needs to be able to decide if the current user is allowed to create that share (and not eg. incerase permissions) + // jfd: AFAICT this can only be determined by a storage driver - either the storage provider is queried first or the share manager needs to access the storage using a storage driver res, err := c.CreateShare(ctx, req) if err != nil { return nil, errors.Wrap(err, "gateway: error calling CreateShare") diff --git a/internal/http/services/owncloud/ocdav/copy.go b/internal/http/services/owncloud/ocdav/copy.go index aad7fdeb4a..b0fecf3c47 100644 --- a/internal/http/services/owncloud/ocdav/copy.go +++ b/internal/http/services/owncloud/ocdav/copy.go @@ -95,7 +95,7 @@ func (s *svc) handleCopy(w http.ResponseWriter, r *http.Request, ns string) { } if srcStatRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, srcStatRes.Status) + HandleErrorStatus(&sublog, w, srcStatRes.Status) return } @@ -111,7 +111,7 @@ func (s *svc) handleCopy(w http.ResponseWriter, r *http.Request, ns string) { return } if dstStatRes.Status.Code != rpc.Code_CODE_OK && dstStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - handleErrorStatus(&sublog, w, srcStatRes.Status) + HandleErrorStatus(&sublog, w, srcStatRes.Status) return } @@ -144,7 +144,7 @@ func (s *svc) handleCopy(w http.ResponseWriter, r *http.Request, ns string) { sublog.Debug().Str("parent", intermediateDir).Interface("status", intStatRes.Status).Msg("conflict") w.WriteHeader(http.StatusConflict) } else { - handleErrorStatus(&sublog, w, srcStatRes.Status) + HandleErrorStatus(&sublog, w, srcStatRes.Status) } return } diff --git a/internal/http/services/owncloud/ocdav/delete.go b/internal/http/services/owncloud/ocdav/delete.go index 2fbc5ab0f0..9083ce5db5 100644 --- a/internal/http/services/owncloud/ocdav/delete.go +++ b/internal/http/services/owncloud/ocdav/delete.go @@ -58,7 +58,7 @@ func (s *svc) handleDelete(w http.ResponseWriter, r *http.Request, ns string) { } if res.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, res.Status) + HandleErrorStatus(&sublog, w, res.Status) return } w.WriteHeader(http.StatusNoContent) diff --git a/internal/http/services/owncloud/ocdav/error.go b/internal/http/services/owncloud/ocdav/error.go index 4d97c97e32..561b91ea40 100644 --- a/internal/http/services/owncloud/ocdav/error.go +++ b/internal/http/services/owncloud/ocdav/error.go @@ -54,7 +54,9 @@ func Marshal(e exception) ([]byte, error) { }) } -func handleErrorStatus(log *zerolog.Logger, w http.ResponseWriter, s *rpc.Status) { +// HandleErrorStatus checks the status code, logs a Debug or Error level message +// and writes an appropriate http status +func HandleErrorStatus(log *zerolog.Logger, w http.ResponseWriter, s *rpc.Status) { switch s.Code { case rpc.Code_CODE_OK: log.Debug().Interface("status", s).Msg("ok") diff --git a/internal/http/services/owncloud/ocdav/get.go b/internal/http/services/owncloud/ocdav/get.go index 35cf8be1a8..484e790354 100644 --- a/internal/http/services/owncloud/ocdav/get.go +++ b/internal/http/services/owncloud/ocdav/get.go @@ -66,7 +66,7 @@ func (s *svc) handleGet(w http.ResponseWriter, r *http.Request, ns string) { } if sRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, sRes.Status) + HandleErrorStatus(&sublog, w, sRes.Status) return } @@ -91,7 +91,7 @@ func (s *svc) handleGet(w http.ResponseWriter, r *http.Request, ns string) { } if dRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, dRes.Status) + HandleErrorStatus(&sublog, w, dRes.Status) return } diff --git a/internal/http/services/owncloud/ocdav/head.go b/internal/http/services/owncloud/ocdav/head.go index 29ff689fde..c7daae2c51 100644 --- a/internal/http/services/owncloud/ocdav/head.go +++ b/internal/http/services/owncloud/ocdav/head.go @@ -61,7 +61,7 @@ func (s *svc) handleHead(w http.ResponseWriter, r *http.Request, ns string) { } if res.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, res.Status) + HandleErrorStatus(&sublog, w, res.Status) return } diff --git a/internal/http/services/owncloud/ocdav/mkcol.go b/internal/http/services/owncloud/ocdav/mkcol.go index a5cc97f331..96b74edb2a 100644 --- a/internal/http/services/owncloud/ocdav/mkcol.go +++ b/internal/http/services/owncloud/ocdav/mkcol.go @@ -71,7 +71,7 @@ func (s *svc) handleMkcol(w http.ResponseWriter, r *http.Request, ns string) { if statRes.Status.Code == rpc.Code_CODE_OK { w.WriteHeader(http.StatusMethodNotAllowed) // 405 if it already exists } else { - handleErrorStatus(&sublog, w, statRes.Status) + HandleErrorStatus(&sublog, w, statRes.Status) } return } @@ -90,6 +90,6 @@ func (s *svc) handleMkcol(w http.ResponseWriter, r *http.Request, ns string) { sublog.Debug().Str("path", fn).Interface("status", statRes.Status).Msg("conflict") w.WriteHeader(http.StatusConflict) default: - handleErrorStatus(&sublog, w, res.Status) + HandleErrorStatus(&sublog, w, res.Status) } } diff --git a/internal/http/services/owncloud/ocdav/move.go b/internal/http/services/owncloud/ocdav/move.go index 75e4d0d5f4..b754314191 100644 --- a/internal/http/services/owncloud/ocdav/move.go +++ b/internal/http/services/owncloud/ocdav/move.go @@ -80,7 +80,7 @@ func (s *svc) handleMove(w http.ResponseWriter, r *http.Request, ns string) { return } if srcStatRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, srcStatRes.Status) + HandleErrorStatus(&sublog, w, srcStatRes.Status) return } @@ -96,7 +96,7 @@ func (s *svc) handleMove(w http.ResponseWriter, r *http.Request, ns string) { return } if dstStatRes.Status.Code != rpc.Code_CODE_OK && dstStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - handleErrorStatus(&sublog, w, srcStatRes.Status) + HandleErrorStatus(&sublog, w, srcStatRes.Status) return } @@ -120,7 +120,7 @@ func (s *svc) handleMove(w http.ResponseWriter, r *http.Request, ns string) { } if delRes.Status.Code != rpc.Code_CODE_OK && delRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - handleErrorStatus(&sublog, w, delRes.Status) + HandleErrorStatus(&sublog, w, delRes.Status) return } } else { @@ -142,7 +142,7 @@ func (s *svc) handleMove(w http.ResponseWriter, r *http.Request, ns string) { sublog.Debug().Str("parent", intermediateDir).Interface("status", intStatRes.Status).Msg("conflict") w.WriteHeader(http.StatusConflict) } else { - handleErrorStatus(&sublog, w, intStatRes.Status) + HandleErrorStatus(&sublog, w, intStatRes.Status) } return } @@ -164,7 +164,7 @@ func (s *svc) handleMove(w http.ResponseWriter, r *http.Request, ns string) { } if mRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, mRes.Status) + HandleErrorStatus(&sublog, w, mRes.Status) return } @@ -176,7 +176,7 @@ func (s *svc) handleMove(w http.ResponseWriter, r *http.Request, ns string) { } if dstStatRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, dstStatRes.Status) + HandleErrorStatus(&sublog, w, dstStatRes.Status) return } diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index d401947a98..5756b57a6b 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -90,7 +90,7 @@ func (s *svc) handlePropfind(w http.ResponseWriter, r *http.Request, ns string) } if res.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, res.Status) + HandleErrorStatus(&sublog, w, res.Status) return } @@ -111,7 +111,7 @@ func (s *svc) handlePropfind(w http.ResponseWriter, r *http.Request, ns string) } if res.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, res.Status) + HandleErrorStatus(&sublog, w, res.Status) return } infos = append(infos, res.Infos...) @@ -138,7 +138,7 @@ func (s *svc) handlePropfind(w http.ResponseWriter, r *http.Request, ns string) return } if res.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, res.Status) + HandleErrorStatus(&sublog, w, res.Status) return } diff --git a/internal/http/services/owncloud/ocdav/proppatch.go b/internal/http/services/owncloud/ocdav/proppatch.go index d957881f98..218bcd5c0f 100644 --- a/internal/http/services/owncloud/ocdav/proppatch.go +++ b/internal/http/services/owncloud/ocdav/proppatch.go @@ -78,7 +78,7 @@ func (s *svc) handleProppatch(w http.ResponseWriter, r *http.Request, ns string) } if statRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, statRes.Status) + HandleErrorStatus(&sublog, w, statRes.Status) return } @@ -128,7 +128,7 @@ func (s *svc) handleProppatch(w http.ResponseWriter, r *http.Request, ns string) } if res.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, res.Status) + HandleErrorStatus(&sublog, w, res.Status) return } removedProps = append(removedProps, propNameXML) @@ -142,7 +142,7 @@ func (s *svc) handleProppatch(w http.ResponseWriter, r *http.Request, ns string) } if res.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, res.Status) + HandleErrorStatus(&sublog, w, res.Status) return } diff --git a/internal/http/services/owncloud/ocdav/publicfile.go b/internal/http/services/owncloud/ocdav/publicfile.go index d13931e89f..a1c071c20c 100644 --- a/internal/http/services/owncloud/ocdav/publicfile.go +++ b/internal/http/services/owncloud/ocdav/publicfile.go @@ -109,7 +109,7 @@ func (s *svc) adjustResourcePathInURL(w http.ResponseWriter, r *http.Request) bo return false } if pathRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, pathRes.Status) + HandleErrorStatus(&sublog, w, pathRes.Status) return false } if path.Base(r.URL.Path) != path.Base(pathRes.Path) { @@ -173,7 +173,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s return } if pathRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, pathRes.Status) + HandleErrorStatus(&sublog, w, pathRes.Status) return } diff --git a/internal/http/services/owncloud/ocdav/put.go b/internal/http/services/owncloud/ocdav/put.go index 7fb5fe813b..b545932bd8 100644 --- a/internal/http/services/owncloud/ocdav/put.go +++ b/internal/http/services/owncloud/ocdav/put.go @@ -167,7 +167,7 @@ func (s *svc) handlePutHelper(w http.ResponseWriter, r *http.Request, content io return } if sRes.Status.Code != rpc.Code_CODE_OK && sRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - handleErrorStatus(&sublog, w, sRes.Status) + HandleErrorStatus(&sublog, w, sRes.Status) return } @@ -220,7 +220,7 @@ func (s *svc) handlePutHelper(w http.ResponseWriter, r *http.Request, content io } if uRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, uRes.Status) + HandleErrorStatus(&sublog, w, uRes.Status) return } @@ -286,7 +286,7 @@ func (s *svc) handlePutHelper(w http.ResponseWriter, r *http.Request, content io } if sRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, sRes.Status) + HandleErrorStatus(&sublog, w, sRes.Status) return } diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index 1f663f69de..e931bef312 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -157,7 +157,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } if getHomeRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, getHomeRes.Status) + HandleErrorStatus(&sublog, w, getHomeRes.Status) return } @@ -178,7 +178,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } if getRecycleRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, getHomeRes.Status) + HandleErrorStatus(&sublog, w, getHomeRes.Status) return } @@ -371,7 +371,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc return } if getHomeRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, getHomeRes.Status) + HandleErrorStatus(&sublog, w, getHomeRes.Status) return } @@ -397,7 +397,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } if res.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, res.Status) + HandleErrorStatus(&sublog, w, res.Status) return } w.WriteHeader(http.StatusNoContent) @@ -425,7 +425,7 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, return } if getHomeRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, getHomeRes.Status) + HandleErrorStatus(&sublog, w, getHomeRes.Status) return } sRes, err := client.Stat(ctx, &provider.StatRequest{ @@ -441,7 +441,7 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, return } if sRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, sRes.Status) + HandleErrorStatus(&sublog, w, sRes.Status) return } @@ -472,6 +472,6 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, sublog.Debug().Str("storageid", sRes.Info.Id.StorageId).Str("key", key).Interface("status", res.Status).Msg("resource not found") w.WriteHeader(http.StatusConflict) default: - handleErrorStatus(&sublog, w, res.Status) + HandleErrorStatus(&sublog, w, res.Status) } } diff --git a/internal/http/services/owncloud/ocdav/tus.go b/internal/http/services/owncloud/ocdav/tus.go index ccdbefd1aa..0c71cf1979 100644 --- a/internal/http/services/owncloud/ocdav/tus.go +++ b/internal/http/services/owncloud/ocdav/tus.go @@ -94,7 +94,7 @@ func (s *svc) handleTusPost(w http.ResponseWriter, r *http.Request, ns string) { } if sRes.Status.Code != rpc.Code_CODE_OK && sRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - handleErrorStatus(&sublog, w, sRes.Status) + HandleErrorStatus(&sublog, w, sRes.Status) return } @@ -150,7 +150,7 @@ func (s *svc) handleTusPost(w http.ResponseWriter, r *http.Request, ns string) { } if uRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, uRes.Status) + HandleErrorStatus(&sublog, w, uRes.Status) return } @@ -231,7 +231,7 @@ func (s *svc) handleTusPost(w http.ResponseWriter, r *http.Request, ns string) { } if sRes.Status.Code != rpc.Code_CODE_OK && sRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - handleErrorStatus(&sublog, w, sRes.Status) + HandleErrorStatus(&sublog, w, sRes.Status) return } diff --git a/internal/http/services/owncloud/ocdav/versions.go b/internal/http/services/owncloud/ocdav/versions.go index e8cff38e85..882babc0f9 100644 --- a/internal/http/services/owncloud/ocdav/versions.go +++ b/internal/http/services/owncloud/ocdav/versions.go @@ -105,7 +105,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, return } if res.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, res.Status) + HandleErrorStatus(&sublog, w, res.Status) return } @@ -121,7 +121,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, return } if lvRes.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, lvRes.Status) + HandleErrorStatus(&sublog, w, lvRes.Status) return } @@ -212,7 +212,7 @@ func (h *VersionsHandler) doRestore(w http.ResponseWriter, r *http.Request, s *s return } if res.Status.Code != rpc.Code_CODE_OK { - handleErrorStatus(&sublog, w, res.Status) + HandleErrorStatus(&sublog, w, res.Status) return } w.WriteHeader(http.StatusNoContent) diff --git a/internal/http/services/owncloud/ocs/conversions/permissions.go b/internal/http/services/owncloud/ocs/conversions/permissions.go index 4d8a544009..731ecb4289 100644 --- a/internal/http/services/owncloud/ocs/conversions/permissions.go +++ b/internal/http/services/owncloud/ocs/conversions/permissions.go @@ -18,7 +18,11 @@ package conversions -import "fmt" +import ( + "fmt" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) // Permissions reflects the CRUD permissions used in the OCS sharing API type Permissions uint @@ -66,12 +70,59 @@ func Permissions2Role(p Permissions) string { role := RoleLegacy if p.Contain(PermissionRead) { role = RoleViewer + if p.Contain(PermissionWrite) && p.Contain(PermissionCreate) && p.Contain(PermissionDelete) { + role = RoleEditor + if p.Contain(PermissionShare) { + role = RoleCoowner + } + } + } + return role +} + +// Role2Permissions converts string roles into ocs permissions +func Role2Permissions(r string) (Permissions, error) { + switch r { + case RoleViewer: + return PermissionRead, nil + case RoleEditor: + return PermissionRead & PermissionWrite & PermissionCreate & PermissionDelete, nil + case RoleCoowner: + return PermissionAll, nil + case RoleLegacy: + return PermissionInvalid, nil // FIXME + default: + return PermissionInvalid, fmt.Errorf("unknown role: %s", r) } - if p.Contain(PermissionWrite) { - role = RoleEditor +} + +// ResourcePermissions2Permissions converts CS3 storage resource permissions into ocs permissions +func ResourcePermissions2Permissions(rp *provider.ResourcePermissions) Permissions { + // TODO what about trash? and versions? + p := PermissionInvalid + if rp.ListContainer == true && + //rp.ListGrants == true && + //rp.ListFileVersions == true && + //rp.ListRecycle == true && + //rp.Stat == true && + //rp.GetPath == true && + //rp.GetQuota == true && + rp.InitiateFileDownload == true { + p = p | PermissionRead } - if p.Contain(PermissionShare) { - role = RoleCoowner + if rp.InitiateFileUpload == true { + p = p | PermissionWrite } - return role + if rp.CreateContainer == true { + p = p | PermissionCreate + } + if rp.Delete == true { + p = p | PermissionDelete + } + if rp.AddGrant == true && + rp.RemoveGrant == true && + rp.UpdateGrant == true { + p = p | PermissionShare + } + return p } diff --git a/internal/http/services/owncloud/ocs/conversions/permissions_test.go b/internal/http/services/owncloud/ocs/conversions/permissions_test.go index 419ebd85d2..05ba0f7409 100644 --- a/internal/http/services/owncloud/ocs/conversions/permissions_test.go +++ b/internal/http/services/owncloud/ocs/conversions/permissions_test.go @@ -65,15 +65,15 @@ func TestContain(t *testing.T) { func TestContainWithMultiplePermissions(t *testing.T) { table := map[int][]Permissions{ - 3: []Permissions{ + 3: { PermissionRead, PermissionWrite, }, - 5: []Permissions{ + 5: { PermissionRead, PermissionCreate, }, - 31: []Permissions{ + 31: { PermissionRead, PermissionWrite, PermissionCreate, diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go index 86a5b6dd2b..9aaaf9d237 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go @@ -22,7 +22,6 @@ import ( "encoding/json" "fmt" "net/http" - "path" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -38,7 +37,7 @@ import ( "github.com/pkg/errors" ) -func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request) { +func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request, statInfo *provider.ResourceInfo) { ctx := r.Context() log := appctx.GetLogger(ctx) @@ -47,39 +46,6 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request) response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) return } - // prefix the path with the owners home, because ocs share requests are relative to the home dir - // TODO the path actually depends on the configured webdav_namespace - hRes, err := c.GetHome(ctx, &provider.GetHomeRequest{}) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc get home request", err) - return - } - - prefix := hRes.GetPath() - - statReq := provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Path{ - Path: path.Join(prefix, r.FormValue("path")), // TODO replace path with target - }, - }, - } - - statRes, err := c.Stat(ctx, &statReq) - if err != nil { - log.Debug().Err(err).Str("createShare", "shares").Msg("error on stat call") - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "missing resource information", fmt.Errorf("error getting resource information")) - return - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "resource not found", fmt.Errorf("error creating share on non-existing resource")) - return - } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error when querying resource", fmt.Errorf("error when querying resource information while creating share, status %d", statRes.Status.Code)) - return - } err = r.ParseForm() if err != nil { @@ -104,7 +70,7 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request) } req := link.CreatePublicShareRequest{ - ResourceInfo: statRes.GetInfo(), + ResourceInfo: statInfo, Grant: &link.Grant{ Permissions: &link.PublicSharePermissions{ Permissions: newPermissions, @@ -137,8 +103,8 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request) createRes, err := c.CreatePublicShare(ctx, &req) if err != nil { - log.Debug().Err(err).Str("createShare", "shares").Msgf("error creating a public share to resource id: %v", statRes.Info.GetId()) - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error creating public share", fmt.Errorf("error creating a public share to resource id: %v", statRes.Info.GetId())) + log.Debug().Err(err).Str("createShare", "shares").Msgf("error creating a public share to resource id: %v", statInfo.GetId()) + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error creating public share", fmt.Errorf("error creating a public share to resource id: %v", statInfo.GetId())) return } @@ -149,7 +115,7 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request) } s := conversions.PublicShare2ShareData(createRes.Share, r, h.publicURL) - err = h.addFileInfo(ctx, s, statRes.Info) + err = h.addFileInfo(ctx, s, statInfo) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error enhancing response with share data", err) return diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go index 341388554f..54d8f6efec 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go @@ -20,7 +20,6 @@ package shares import ( "net/http" - "path" "strconv" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -37,7 +36,7 @@ import ( "github.com/cs3org/reva/pkg/rgrpc/todo/pool" ) -func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Request) { +func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Request, statInfo *provider.ResourceInfo) { ctx := r.Context() log := appctx.GetLogger(ctx) @@ -46,15 +45,6 @@ func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Reque response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) return } - // prefix the path with the owners home, because ocs share requests are relative to the home dir - // TODO the path actually depends on the configured webdav_namespace - hRes, err := c.GetHome(ctx, &provider.GetHomeRequest{}) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc get home request", err) - return - } - - prefix := hRes.GetPath() shareWithUser, shareWithProvider := r.FormValue("shareWithUser"), r.FormValue("shareWithProvider") if shareWithUser == "" || shareWithProvider == "" { @@ -111,41 +101,20 @@ func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Reque resourcePermissions = asCS3Permissions(permissions, nil) } - statReq := &provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Path{ - Path: path.Join(prefix, r.FormValue("path")), - }, - }, - } - statRes, err := c.Stat(ctx, statReq) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc stat request", err) - return - } - if statRes.Status.Code != rpc.Code_CODE_OK { - if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) - return - } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc stat request failed", err) - return - } - createShareReq := &ocm.CreateOCMShareRequest{ Opaque: &types.Opaque{ Map: map[string]*types.OpaqueEntry{ - "permissions": &types.OpaqueEntry{ + "permissions": { Decoder: "plain", Value: []byte(strconv.Itoa(int(permissions))), }, - "name": &types.OpaqueEntry{ + "name": { Decoder: "plain", - Value: []byte(statRes.Info.Path), + Value: []byte(statInfo.Path), }, }, }, - ResourceId: statRes.Info.Id, + ResourceId: statInfo.Id, Grant: &ocm.ShareGrant{ Grantee: &provider.Grantee{ Type: provider.GranteeType_GRANTEE_TYPE_USER, diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index 442b30893e..c6bdba127c 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -21,7 +21,6 @@ package shares import ( "context" "encoding/base64" - "encoding/json" "fmt" "mime" "net/http" @@ -39,6 +38,7 @@ import ( types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/rs/zerolog/log" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/config" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" @@ -149,26 +149,14 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func (h *Handler) createShare(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() shareType, err := strconv.Atoi(r.FormValue("shareType")) if err != nil { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "shareType must be an integer", nil) return } + // TODO get user permissions on the shared file - switch shareType { - case int(conversions.ShareTypeUser): - h.createUserShare(w, r) - case int(conversions.ShareTypePublicLink): - h.createPublicLinkShare(w, r) - case int(conversions.ShareTypeFederatedCloudShare): - h.createFederatedCloudShare(w, r) - default: - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "unknown share type", nil) - } -} - -func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() c, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) @@ -183,141 +171,95 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request) { } prefix := hRes.GetPath() - sharepath := r.FormValue("path") - // if user sharing is disabled - if h.gatewayAddr == "" { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "user sharing service not configured", nil) - return - } + fn := path.Join(prefix, r.FormValue("path")) - shareWith := r.FormValue("shareWith") - if shareWith == "" { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "missing shareWith", nil) - return + statReq := provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: fn, + }, + }, } - userRes, err := c.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ - Claim: "username", - Value: shareWith, - }) + sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() + + statRes, err := c.Stat(ctx, &statReq) if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching recipient", err) + sublog.Debug().Err(err).Str("createShare", "shares").Msg("error on stat call") + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "missing resource information", fmt.Errorf("error getting resource information")) return } - if userRes.Status.Code != rpc.Code_CODE_OK { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "user not found", err) + if statRes.Status.Code != rpc.Code_CODE_OK { + ocdav.HandleErrorStatus(&sublog, w, statRes.Status) return } - statRes, err := h.stat(ctx, path.Join(prefix, sharepath)) - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, fmt.Sprintf("stat on file %s failed", sharepath), err) + if !h.validatePermissions(w, r, statRes.Info.PermissionSet) { return } - var permissions conversions.Permissions + // TODO check the share permissions do not extend the user permissions + + switch shareType { + case int(conversions.ShareTypeUser): + h.createUserShare(w, r, statRes.Info) + case int(conversions.ShareTypePublicLink): + h.createPublicLinkShare(w, r, statRes.Info) + case int(conversions.ShareTypeFederatedCloudShare): + h.createFederatedCloudShare(w, r, statRes.Info) + default: + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "unknown share type", nil) + } +} + +func (h *Handler) validatePermissions(w http.ResponseWriter, r *http.Request, statPerms *provider.ResourcePermissions) bool { + + // 1. we start without permissions + var reqPermissions conversions.Permissions + var err error + + reqRole := r.FormValue("role") - role := r.FormValue("role") - // 2. if we don't have a role try to map the permissions - if role == "" { + // the share role overrides the requested permissions + if reqRole != "" && reqRole != conversions.RoleLegacy { + reqPermissions, err = conversions.Role2Permissions(reqRole) + if err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, err.Error(), nil) + return false + } + } else { + // map requested permissions pval := r.FormValue("permissions") if pval == "" { - // default is all permissions / role coowner - permissions = conversions.PermissionAll - role = conversions.RoleCoowner + // default is read permissions / role viewer + reqPermissions = conversions.PermissionAll + reqRole = conversions.RoleCoowner } else { pint, err := strconv.Atoi(pval) if err != nil { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "permissions must be an integer", nil) - return + return false } - permissions, err = conversions.NewPermissions(pint) + reqPermissions, err = conversions.NewPermissions(pint) if err != nil { if err == conversions.ErrPermissionNotInRange { response.WriteOCSError(w, r, http.StatusNotFound, err.Error(), nil) } else { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, err.Error(), nil) } - return + return false } - role = conversions.Permissions2Role(permissions) - } - } - - if statRes.Info != nil && statRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_FILE { - // Single file shares should never have delete or create permissions - permissions &^= conversions.PermissionCreate - permissions &^= conversions.PermissionDelete - } - - var resourcePermissions *provider.ResourcePermissions - resourcePermissions = asCS3Permissions(permissions, resourcePermissions) - - roleMap := map[string]string{"name": role} - val, err := json.Marshal(roleMap) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "could not encode role", err) - return - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) - return + reqRole = conversions.Permissions2Role(reqPermissions) } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc stat request failed", err) - return - } - - createShareReq := &collaboration.CreateShareRequest{ - Opaque: &types.Opaque{ - Map: map[string]*types.OpaqueEntry{ - "role": { - Decoder: "json", - Value: val, - }, - }, - }, - ResourceInfo: statRes.Info, - Grant: &collaboration.ShareGrant{ - Grantee: &provider.Grantee{ - Type: provider.GranteeType_GRANTEE_TYPE_USER, - Id: userRes.User.GetId(), - }, - Permissions: &collaboration.SharePermissions{ - Permissions: resourcePermissions, - }, - }, } - createShareResponse, err := c.CreateShare(ctx, createShareReq) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc create share request", err) - return - } - if createShareResponse.Status.Code != rpc.Code_CODE_OK { - if createShareResponse.Status.Code == rpc.Code_CODE_NOT_FOUND { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) - return - } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc create share request failed", err) - return - } - s, err := conversions.UserShare2ShareData(ctx, createShareResponse.Share) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err) - return + existingPermissions := conversions.ResourcePermissions2Permissions(statPerms) + if !existingPermissions.Contain(reqPermissions) { + response.WriteOCSError(w, r, http.StatusNotFound, "Cannot set the requested share permissions", nil) + return false } - err = h.addFileInfo(ctx, s, statRes.Info) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error adding fileinfo to share", err) - return - } - h.addDisplaynames(ctx, c, s) - h.mapUserIds(ctx, c, s) - - response.WriteOCSSuccess(w, r, s) + return true } func (h *Handler) stat(ctx context.Context, path string) (*provider.StatResponse, error) { diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go index 056cb48561..f5decf20dd 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go @@ -19,11 +19,15 @@ package shares import ( + "encoding/json" "net/http" + "strconv" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" @@ -31,6 +35,128 @@ import ( "github.com/cs3org/reva/pkg/rgrpc/todo/pool" ) +func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statInfo *provider.ResourceInfo) { + ctx := r.Context() + c, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) + return + } + + var permissions conversions.Permissions + + role := r.FormValue("role") + // 2. if we don't have a role try to map the permissions + if role == "" { + pval := r.FormValue("permissions") + if pval == "" { + // default is all permissions / role coowner + permissions = conversions.PermissionAll + role = conversions.RoleCoowner + } else { + pint, err := strconv.Atoi(pval) + if err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "permissions must be an integer", nil) + return + } + permissions, err = conversions.NewPermissions(pint) + if err != nil { + if err == conversions.ErrPermissionNotInRange { + response.WriteOCSError(w, r, http.StatusNotFound, err.Error(), nil) + } else { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, err.Error(), nil) + } + return + } + role = conversions.Permissions2Role(permissions) + } + } + + if statInfo != nil && statInfo.Type == provider.ResourceType_RESOURCE_TYPE_FILE { + // Single file shares should never have delete or create permissions + permissions &^= conversions.PermissionCreate + permissions &^= conversions.PermissionDelete + } + + var resourcePermissions *provider.ResourcePermissions + resourcePermissions = asCS3Permissions(permissions, resourcePermissions) + + roleMap := map[string]string{"name": role} + val, err := json.Marshal(roleMap) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "could not encode role", err) + return + } + + shareWith := r.FormValue("shareWith") + if shareWith == "" { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "missing shareWith", nil) + return + } + + userRes, err := c.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ + Claim: "username", + Value: shareWith, + }) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching recipient", err) + return + } + + if userRes.Status.Code != rpc.Code_CODE_OK { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "user not found", err) + return + } + + createShareReq := &collaboration.CreateShareRequest{ + Opaque: &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "role": { + Decoder: "json", + Value: val, + }, + }, + }, + ResourceInfo: statInfo, + Grant: &collaboration.ShareGrant{ + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: userRes.User.GetId(), + }, + Permissions: &collaboration.SharePermissions{ + Permissions: resourcePermissions, + }, + }, + } + + createShareResponse, err := c.CreateShare(ctx, createShareReq) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc create share request", err) + return + } + if createShareResponse.Status.Code != rpc.Code_CODE_OK { + if createShareResponse.Status.Code == rpc.Code_CODE_NOT_FOUND { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) + return + } + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc create share request failed", err) + return + } + s, err := conversions.UserShare2ShareData(ctx, createShareResponse.Share) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err) + return + } + err = h.addFileInfo(ctx, s, statInfo) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error adding fileinfo to share", err) + return + } + h.addDisplaynames(ctx, c, s) + + response.WriteOCSSuccess(w, r, s) +} + func (h *Handler) removeUserShare(w http.ResponseWriter, r *http.Request, shareID string) { ctx := r.Context() diff --git a/pkg/storage/fs/ocis/node.go b/pkg/storage/fs/ocis/node.go index d8547aff7f..d749f99754 100644 --- a/pkg/storage/fs/ocis/node.go +++ b/pkg/storage/fs/ocis/node.go @@ -36,6 +36,7 @@ import ( "github.com/cs3org/reva/pkg/mime" "github.com/cs3org/reva/pkg/sdk/common" "github.com/cs3org/reva/pkg/storage/utils/ace" + "github.com/cs3org/reva/pkg/user" "github.com/pkg/errors" "github.com/pkg/xattr" "github.com/rs/zerolog/log" @@ -296,6 +297,25 @@ func (n *Node) Owner() (id string, idp string, err error) { return n.ownerID, n.ownerIDP, err } +// PermissionSet returns the permission set for the curren user +func (n *Node) PermissionSet(ctx context.Context) *provider.ResourcePermissions { + u, ok := user.ContextGetUser(ctx) + if !ok { + appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("no user in context, returning default permissions") + return defaultPermissions + } + if u.Id.OpaqueId == n.ownerID && u.Id.Idp == n.ownerIDP { + return ownerPermissions + } + // TODO fix permissions for share recipients by traversing up to the next acl? or to the root? + // ouch ... if we have to read acls for every dir entry that would be n*depth additional xattr reads ... + // but we only need to read the parent tree once. + return &provider.ResourcePermissions{ + ListContainer: true, + CreateContainer: true, + } +} + // AsResourceInfo return the node as CS3 ResourceInfo func (n *Node) AsResourceInfo(ctx context.Context, mdKeys []string) (ri *provider.ResourceInfo, err error) { log := appctx.GetLogger(ctx) @@ -339,9 +359,7 @@ func (n *Node) AsResourceInfo(ctx context.Context, mdKeys []string) (ri *provide Type: nodeType, MimeType: mime.Detect(nodeType == provider.ResourceType_RESOURCE_TYPE_CONTAINER, fn), Size: uint64(fi.Size()), - // TODO fix permissions - PermissionSet: &provider.ResourcePermissions{ListContainer: true, CreateContainer: true}, - Target: string(target), + Target: string(target), } if owner, idp, err := n.Owner(); err == nil { @@ -351,6 +369,8 @@ func (n *Node) AsResourceInfo(ctx context.Context, mdKeys []string) (ri *provide } } + ri.PermissionSet = n.PermissionSet(ctx) + // etag currently is a hash of fileid + tmtime (or mtime) // TODO make etag of files use fileid and checksum // TODO implment adding temporery etag in an attribute to restore backups diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 7f1b02482e..8952f68489 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1310,6 +1310,51 @@ func (fs *eosfs) convertToFileReference(ctx context.Context, eosFileInfo *eoscli return info, nil } +// permissionSet returns the permission set for the curren user +func (fs *eosfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provider.ResourcePermissions { + u, ok := user.ContextGetUser(ctx) + if !ok { + appctx.GetLogger(ctx).Debug().Msg("no user in context, returning default permissions (none)") + return &provider.ResourcePermissions{ + // no permissions + } + } + if u.Id == nil { + appctx.GetLogger(ctx).Error().Msg("user has no id, returning default permissions (none)") + return &provider.ResourcePermissions{ + // no permissions + } + } + if u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp { + return &provider.ResourcePermissions{ + // owner has all all permissions + AddGrant: true, + CreateContainer: true, + Delete: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + InitiateFileUpload: true, + ListContainer: true, + ListFileVersions: true, + ListGrants: true, + ListRecycle: true, + Move: true, + PurgeRecycle: true, + RemoveGrant: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + Stat: true, + UpdateGrant: true, + } + } + // TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it + return &provider.ResourcePermissions{ + ListContainer: true, + CreateContainer: true, + } +} + func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) { path, err := fs.unwrap(ctx, eosFileInfo.File) if err != nil { @@ -1321,28 +1366,28 @@ func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo) ( size = eosFileInfo.TreeSize } - username, err := fs.getUserIDGateway(ctx, strconv.FormatUint(eosFileInfo.UID, 10)) + owner, err := fs.getUserIDGateway(ctx, strconv.FormatUint(eosFileInfo.UID, 10)) if err != nil { log := appctx.GetLogger(ctx) log.Warn().Uint64("uid", eosFileInfo.UID).Msg("could not lookup userid, leaving empty") - username = &userpb.UserId{} + owner = &userpb.UserId{} } info := &provider.ResourceInfo{ Id: &provider.ResourceId{OpaqueId: fmt.Sprintf("%d", eosFileInfo.Inode)}, Path: path, - Owner: username, + Owner: owner, Etag: fmt.Sprintf("\"%s\"", strings.Trim(eosFileInfo.ETag, "\"")), MimeType: mime.Detect(eosFileInfo.IsDir, path), Size: size, - PermissionSet: &provider.ResourcePermissions{ListContainer: true, CreateContainer: true}, + PermissionSet: fs.permissionSet(ctx, owner), Mtime: &types.Timestamp{ Seconds: eosFileInfo.MTimeSec, Nanos: eosFileInfo.MTimeNanos, }, Opaque: &types.Opaque{ Map: map[string]*types.OpaqueEntry{ - "eos": &types.OpaqueEntry{ + "eos": { Decoder: "json", Value: fs.getEosMetadata(eosFileInfo), }, From a0cd57f762aa1cdcc7afbf89455dd7906ff5d945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 10 Dec 2020 20:43:36 +0000 Subject: [PATCH 02/41] refactor permission mapping to a role struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- internal/http/services/ocmd/shares.go | 21 +- .../services/owncloud/ocs/conversions/main.go | 116 ------ .../owncloud/ocs/conversions/permissions.go | 64 ---- .../ocs/conversions/permissions_test.go | 12 +- .../services/owncloud/ocs/conversions/role.go | 332 ++++++++++++++++++ .../handlers/apps/sharing/shares/public.go | 73 ++++ .../handlers/apps/sharing/shares/remote.go | 29 +- .../handlers/apps/sharing/shares/shares.go | 168 +-------- .../ocs/handlers/apps/sharing/shares/user.go | 28 +- 9 files changed, 452 insertions(+), 391 deletions(-) create mode 100644 internal/http/services/owncloud/ocs/conversions/role.go diff --git a/internal/http/services/ocmd/shares.go b/internal/http/services/ocmd/shares.go index 108886a4d9..a382e49c0f 100644 --- a/internal/http/services/ocmd/shares.go +++ b/internal/http/services/ocmd/shares.go @@ -29,7 +29,6 @@ import ( ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" 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/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/pkg/appctx" @@ -87,7 +86,7 @@ func (h *sharesHandler) createShare(w http.ResponseWriter, r *http.Request) { providerInfo := ocmprovider.ProviderInfo{ Domain: meshProvider, Services: []*ocmprovider.Service{ - &ocmprovider.Service{ + { Host: clientIP, }, }, @@ -124,7 +123,7 @@ func (h *sharesHandler) createShare(w http.ResponseWriter, r *http.Request) { } var permissions conversions.Permissions - var role, token string + var token string options, ok := protocolDecoded["options"].(map[string]interface{}) if !ok { WriteError(w, r, APIErrorInvalidParameter, "protocol: webdav token not provided", nil) @@ -137,24 +136,20 @@ func (h *sharesHandler) createShare(w http.ResponseWriter, r *http.Request) { return } + var role *conversions.Role pval, ok := options["permissions"].(int) if !ok { - role = conversions.RoleViewer + role = conversions.NewViewerRole() } else { permissions, err = conversions.NewPermissions(pval) if err != nil { WriteError(w, r, APIErrorInvalidParameter, err.Error(), nil) return } - role = conversions.Permissions2Role(permissions) + role = conversions.RoleFromOCSPermissions(permissions) } - var resourcePermissions *provider.ResourcePermissions - resourcePermissions, err = conversions.Role2CS3Permissions(role) - if err != nil { - WriteError(w, r, APIErrorInvalidParameter, "unknown role", err) - } - val, err := json.Marshal(resourcePermissions) + val, err := json.Marshal(role.CS3ResourcePermissions()) if err != nil { WriteError(w, r, APIErrorServerError, "could not encode role", nil) return @@ -173,11 +168,11 @@ func (h *sharesHandler) createShare(w http.ResponseWriter, r *http.Request) { Name: protocolDecoded["name"].(string), Opaque: &types.Opaque{ Map: map[string]*types.OpaqueEntry{ - "permissions": &types.OpaqueEntry{ + "permissions": { Decoder: "json", Value: val, }, - "token": &types.OpaqueEntry{ + "token": { Decoder: "plain", Value: []byte(token), }, diff --git a/internal/http/services/owncloud/ocs/conversions/main.go b/internal/http/services/owncloud/ocs/conversions/main.go index 4f74d924cf..07d0bff147 100644 --- a/internal/http/services/owncloud/ocs/conversions/main.go +++ b/internal/http/services/owncloud/ocs/conversions/main.go @@ -168,111 +168,6 @@ type MatchValueData struct { ShareWith string `json:"shareWith" xml:"shareWith"` } -// Role2CS3Permissions converts string roles (from the request body) into cs3 permissions -// TODO(refs) consider using a mask instead of booleans here, might reduce all this boilerplate -func Role2CS3Permissions(r string) (*provider.ResourcePermissions, error) { - switch r { - case RoleViewer: - return &provider.ResourcePermissions{ - ListContainer: true, - ListGrants: true, - ListFileVersions: true, - ListRecycle: true, - Stat: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - }, nil - case RoleEditor: - return &provider.ResourcePermissions{ - ListContainer: true, - ListGrants: true, - ListFileVersions: true, - ListRecycle: true, - Stat: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - - Move: true, - InitiateFileUpload: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - CreateContainer: true, - Delete: true, - PurgeRecycle: true, - }, nil - case RoleCoowner: - return &provider.ResourcePermissions{ - ListContainer: true, - ListGrants: true, - ListFileVersions: true, - ListRecycle: true, - Stat: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - - Move: true, - InitiateFileUpload: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - CreateContainer: true, - Delete: true, - PurgeRecycle: true, - - AddGrant: true, - RemoveGrant: true, // TODO when are you able to unshare / delete - UpdateGrant: true, - }, nil - default: - return nil, fmt.Errorf("unknown role: %s", r) - } -} - -// AsCS3Permissions returns permission values as cs3api permissions -// TODO sort out mapping, this is just a first guess -// TODO use roles to make this configurable -func AsCS3Permissions(p int, rp *provider.ResourcePermissions) *provider.ResourcePermissions { - if rp == nil { - rp = &provider.ResourcePermissions{} - } - - if p&int(PermissionRead) != 0 { - rp.ListContainer = true - rp.ListGrants = true - rp.ListFileVersions = true - rp.ListRecycle = true - rp.Stat = true - rp.GetPath = true - rp.GetQuota = true - rp.InitiateFileDownload = true - } - if p&int(PermissionWrite) != 0 { - rp.InitiateFileUpload = true - rp.RestoreFileVersion = true - rp.RestoreRecycleItem = true - } - if p&int(PermissionCreate) != 0 { - rp.CreateContainer = true - // FIXME permissions mismatch: double check create vs write file - rp.InitiateFileUpload = true - if p&int(PermissionWrite) != 0 { - rp.Move = true // TODO move only when create and write? - } - } - if p&int(PermissionDelete) != 0 { - rp.Delete = true - rp.PurgeRecycle = true - } - if p&int(PermissionShare) != 0 { - rp.AddGrant = true - rp.RemoveGrant = true // TODO when are you able to unshare / delete - rp.UpdateGrant = true - } - return rp -} - // UserShare2ShareData converts a cs3api user share into shareData data model // TODO(jfd) merge userShare2ShareData with publicShare2ShareData func UserShare2ShareData(ctx context.Context, share *collaboration.Share) (*ShareData, error) { @@ -415,14 +310,3 @@ func Permissions2OCSPermissions(p *provider.ResourcePermissions) Permissions { func timestampToExpiration(t *types.Timestamp) string { return time.Unix(int64(t.Seconds), int64(t.Nanos)).UTC().Format("2006-01-02 15:05:05") } - -const ( - // RoleLegacy provides backwards compatibility - RoleLegacy string = "legacy" - // RoleViewer grants non-editor role on a resource - RoleViewer string = "viewer" - // RoleEditor grants editor permission on a resource - RoleEditor string = "editor" - // RoleCoowner grants owner permissions on a resource - RoleCoowner string = "coowner" -) diff --git a/internal/http/services/owncloud/ocs/conversions/permissions.go b/internal/http/services/owncloud/ocs/conversions/permissions.go index 731ecb4289..e43c1e3a57 100644 --- a/internal/http/services/owncloud/ocs/conversions/permissions.go +++ b/internal/http/services/owncloud/ocs/conversions/permissions.go @@ -20,8 +20,6 @@ package conversions import ( "fmt" - - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" ) // Permissions reflects the CRUD permissions used in the OCS sharing API @@ -64,65 +62,3 @@ func NewPermissions(val int) (Permissions, error) { func (p Permissions) Contain(other Permissions) bool { return p&other != 0 } - -// Permissions2Role performs permission conversions for user and federated shares -func Permissions2Role(p Permissions) string { - role := RoleLegacy - if p.Contain(PermissionRead) { - role = RoleViewer - if p.Contain(PermissionWrite) && p.Contain(PermissionCreate) && p.Contain(PermissionDelete) { - role = RoleEditor - if p.Contain(PermissionShare) { - role = RoleCoowner - } - } - } - return role -} - -// Role2Permissions converts string roles into ocs permissions -func Role2Permissions(r string) (Permissions, error) { - switch r { - case RoleViewer: - return PermissionRead, nil - case RoleEditor: - return PermissionRead & PermissionWrite & PermissionCreate & PermissionDelete, nil - case RoleCoowner: - return PermissionAll, nil - case RoleLegacy: - return PermissionInvalid, nil // FIXME - default: - return PermissionInvalid, fmt.Errorf("unknown role: %s", r) - } -} - -// ResourcePermissions2Permissions converts CS3 storage resource permissions into ocs permissions -func ResourcePermissions2Permissions(rp *provider.ResourcePermissions) Permissions { - // TODO what about trash? and versions? - p := PermissionInvalid - if rp.ListContainer == true && - //rp.ListGrants == true && - //rp.ListFileVersions == true && - //rp.ListRecycle == true && - //rp.Stat == true && - //rp.GetPath == true && - //rp.GetQuota == true && - rp.InitiateFileDownload == true { - p = p | PermissionRead - } - if rp.InitiateFileUpload == true { - p = p | PermissionWrite - } - if rp.CreateContainer == true { - p = p | PermissionCreate - } - if rp.Delete == true { - p = p | PermissionDelete - } - if rp.AddGrant == true && - rp.RemoveGrant == true && - rp.UpdateGrant == true { - p = p | PermissionShare - } - return p -} diff --git a/internal/http/services/owncloud/ocs/conversions/permissions_test.go b/internal/http/services/owncloud/ocs/conversions/permissions_test.go index 05ba0f7409..e54eaa4a9a 100644 --- a/internal/http/services/owncloud/ocs/conversions/permissions_test.go +++ b/internal/http/services/owncloud/ocs/conversions/permissions_test.go @@ -100,16 +100,16 @@ func TestPermissions2Role(t *testing.T) { } table := map[Permissions]string{ - PermissionRead: RoleViewer, - PermissionWrite: RoleEditor, - PermissionShare: RoleCoowner, + PermissionRead: RoleViewer, + PermissionRead | PermissionWrite | PermissionCreate | PermissionDelete: RoleEditor, PermissionAll: RoleCoowner, - PermissionRead | PermissionWrite: RoleEditor, - PermissionWrite | PermissionShare: RoleCoowner, + PermissionWrite: RoleLegacy, + PermissionShare: RoleLegacy, + PermissionWrite | PermissionShare: RoleLegacy, } for permissions, role := range table { - actual := Permissions2Role(permissions) + actual := RoleFromOCSPermissions(permissions).Name checkRole(role, actual) } } diff --git a/internal/http/services/owncloud/ocs/conversions/role.go b/internal/http/services/owncloud/ocs/conversions/role.go new file mode 100644 index 0000000000..4a920ae971 --- /dev/null +++ b/internal/http/services/owncloud/ocs/conversions/role.go @@ -0,0 +1,332 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// Package conversions sits between CS3 type definitions and OCS API Responses +package conversions + +import provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + +// Role describes the interface to transform different permission sets into each other +type Role struct { + Name string + cS3ResourcePermissions *provider.ResourcePermissions + ocsPermissions Permissions +} + +const ( + // RoleUnknown is used for unknown roles + RoleUnknown string = "unknown" + // RoleLegacy provides backwards compatibility + RoleLegacy string = "legacy" + // RoleViewer grants non-editor role on a resource + RoleViewer string = "viewer" + // RoleEditor grants editor permission on a resource, including folders + RoleEditor string = "editor" + // RoleFileEditor grants editor permission on a single file + RoleFileEditor string = "file-editor" + // RoleCoowner grants owner permissions on a resource + RoleCoowner string = "coowner" + // RoleUploader FIXME: uploader role with only write permission can use InitiateFileUpload, not anything else + RoleUploader string = "uploader" +) + +// CS3ResourcePermissions for the role +func (r *Role) CS3ResourcePermissions() *provider.ResourcePermissions { + return r.cS3ResourcePermissions +} + +// OCSPermissions for the role +func (r *Role) OCSPermissions() Permissions { + return r.OCSPermissions() +} + +// RoleFromName creates a role from the name +func RoleFromName(name string) *Role { + switch name { + case RoleViewer: + return NewViewerRole() + case RoleEditor: + return NewEditorRole() + case RoleFileEditor: + return NewFileEditorRole() + case RoleCoowner: + return NewCoownerRole() + case RoleUploader: + return NewUploaderRole() + } + return NewUnknownRole() +} + +// NewUnknownRole creates an unknown role +func NewUnknownRole() *Role { + return &Role{ + Name: RoleUnknown, + cS3ResourcePermissions: &provider.ResourcePermissions{}, + ocsPermissions: PermissionInvalid, + } +} + +// NewViewerRole creates a viewer role +func NewViewerRole() *Role { + return &Role{ + Name: RoleViewer, + cS3ResourcePermissions: &provider.ResourcePermissions{ + // read + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + ListGrants: true, + ListContainer: true, + ListFileVersions: true, + ListRecycle: true, + Stat: true, + }, + ocsPermissions: PermissionRead, + } +} + +// NewEditorRole creates an editor role +func NewEditorRole() *Role { + return &Role{ + Name: RoleEditor, + cS3ResourcePermissions: &provider.ResourcePermissions{ + // read + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + ListGrants: true, + ListContainer: true, + ListFileVersions: true, + ListRecycle: true, + Stat: true, + + // write + InitiateFileUpload: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + + // create + CreateContainer: true, + + // delete + Delete: true, + + // not sure where to put these, but they are part of an editor + Move: true, + PurgeRecycle: true, + }, + ocsPermissions: PermissionRead | PermissionCreate | PermissionWrite | PermissionDelete, + } +} + +// NewFileEditorRole creates a file-editor role +func NewFileEditorRole() *Role { + return &Role{ + Name: RoleEditor, + cS3ResourcePermissions: &provider.ResourcePermissions{ + // read + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + ListGrants: true, + ListContainer: true, + ListFileVersions: true, + ListRecycle: true, + Stat: true, + + // write + InitiateFileUpload: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + }, + ocsPermissions: PermissionRead | PermissionWrite, + } +} + +// NewCoownerRole creates a coowner role +func NewCoownerRole() *Role { + return &Role{ + Name: RoleCoowner, + cS3ResourcePermissions: &provider.ResourcePermissions{ + // read + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + ListGrants: true, + ListContainer: true, + ListFileVersions: true, + ListRecycle: true, + Stat: true, + + // write + InitiateFileUpload: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + + // create + CreateContainer: true, + + // delete + Delete: true, + + // not sure where to put these, but they are part of an editor + Move: true, + PurgeRecycle: true, + + // grants + AddGrant: true, + UpdateGrant: true, + RemoveGrant: true, + }, + ocsPermissions: PermissionAll, + } +} + +// NewUploaderRole creates an uploader role +// TODO check this works properly +func NewUploaderRole() *Role { + return &Role{ + Name: RoleViewer, + cS3ResourcePermissions: &provider.ResourcePermissions{ + // read + GetPath: true, + // he will need to make stat requests + Stat: true, + // upload + InitiateFileUpload: true, + }, + ocsPermissions: PermissionWrite, + } +} + +// RoleFromOCSPermissions tries to map ocs permissions to a role +func RoleFromOCSPermissions(p Permissions) *Role { + role := "" + if p.Contain(PermissionRead) { + role = RoleViewer + if p.Contain(PermissionWrite | PermissionCreate | PermissionDelete) { + role = RoleEditor + if p.Contain(PermissionShare) { + role = RoleCoowner + } + return RoleFromName(role) // editor or coowner + } + if p == PermissionRead { + return NewViewerRole() + } + } + role = RoleLegacy + // legacy + return NewLegacyRoleFromOCSPermissions(p) +} + +// NewLegacyRoleFromOCSPermissions tries to map ocs a lagecy combination of ocs permissions to cs3 resource permissions as a legacy role +func NewLegacyRoleFromOCSPermissions(p Permissions) *Role { + r := &Role{ + Name: RoleLegacy, // TODO custom role? + ocsPermissions: p, + cS3ResourcePermissions: &provider.ResourcePermissions{}, + } + if p.Contain(PermissionRead) { + r.cS3ResourcePermissions.ListContainer = true + r.cS3ResourcePermissions.ListGrants = true + r.cS3ResourcePermissions.ListFileVersions = true + r.cS3ResourcePermissions.ListRecycle = true + r.cS3ResourcePermissions.Stat = true + r.cS3ResourcePermissions.GetPath = true + r.cS3ResourcePermissions.GetQuota = true + r.cS3ResourcePermissions.InitiateFileDownload = true + } + if p.Contain(PermissionWrite) { + r.cS3ResourcePermissions.InitiateFileUpload = true + r.cS3ResourcePermissions.RestoreFileVersion = true + r.cS3ResourcePermissions.RestoreRecycleItem = true + } + if p.Contain(PermissionCreate) { + r.cS3ResourcePermissions.CreateContainer = true + // FIXME permissions mismatch: double check create vs write file + r.cS3ResourcePermissions.InitiateFileUpload = true + if p.Contain(PermissionWrite) { + r.cS3ResourcePermissions.Move = true // TODO move only when create and write? + } + } + if p.Contain(PermissionDelete) { + r.cS3ResourcePermissions.Delete = true + r.cS3ResourcePermissions.PurgeRecycle = true + } + if p.Contain(PermissionShare) { + r.cS3ResourcePermissions.AddGrant = true + r.cS3ResourcePermissions.RemoveGrant = true // TODO when are you able to unshare / delete + r.cS3ResourcePermissions.UpdateGrant = true + } + return r +} + +// RoleFromResourcePermissions tries to map cs3 resource permissions to a role +func RoleFromResourcePermissions(rp *provider.ResourcePermissions) *Role { + r := &Role{ + Name: RoleLegacy, + ocsPermissions: PermissionInvalid, + cS3ResourcePermissions: rp, + } + if rp.ListContainer && + rp.ListGrants && + rp.ListFileVersions && + rp.ListRecycle && + rp.Stat && + rp.GetPath && + rp.GetQuota && + rp.InitiateFileDownload { + r.ocsPermissions = r.ocsPermissions | PermissionRead + } + if rp.InitiateFileUpload && + rp.RestoreFileVersion && + rp.RestoreRecycleItem { + r.ocsPermissions = r.ocsPermissions | PermissionWrite + } + if rp.CreateContainer && + rp.InitiateFileUpload { + r.ocsPermissions = r.ocsPermissions | PermissionCreate + } + if rp.Delete && + rp.PurgeRecycle { + r.ocsPermissions = r.ocsPermissions | PermissionDelete + } + if rp.AddGrant && + rp.RemoveGrant && + rp.UpdateGrant { + r.ocsPermissions = r.ocsPermissions | PermissionShare + } + if r.ocsPermissions.Contain(PermissionRead) { + if r.ocsPermissions.Contain(PermissionWrite | PermissionCreate | PermissionDelete) { + r.Name = RoleEditor + if r.ocsPermissions.Contain(PermissionShare) { + r.Name = RoleCoowner + } + return r // editor or coowner + } + if r.ocsPermissions == PermissionRead { + r.Name = RoleViewer + return r + } + } + r.Name = RoleLegacy + // at this point other ocs permissions may have been mapped. + // TODO what about even more granular cs3 permissions?, eg. only stat + return r +} diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go index 9aaaf9d237..ddf30c041a 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go @@ -22,6 +22,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -437,3 +438,75 @@ func (h *Handler) removePublicShare(w http.ResponseWriter, r *http.Request, shar response.WriteOCSSuccess(w, r, nil) } + +func ocPublicPermToCs3(permKey int, h *Handler) (*provider.ResourcePermissions, error) { + role, ok := ocPublicPermToRole[permKey] + if !ok { + log.Error().Str("ocPublicPermToCs3", "shares").Msgf("invalid oC permission: %s", role) + return nil, fmt.Errorf("invalid oC permission: %s", role) + } + + perm, err := conversions.NewPermissions(permKey) + if err != nil { + return nil, err + } + + return conversions.RoleFromOCSPermissions(perm).CS3ResourcePermissions(), nil +} + +func permissionFromRequest(r *http.Request, h *Handler) (*provider.ResourcePermissions, error) { + var err error + // phoenix sends: {"permissions": 15}. See ocPublicPermToRole struct for mapping + + permKey := 1 + + // note: "permissions" value has higher priority than "publicUpload" + + // handle legacy "publicUpload" arg that overrides permissions differently depending on the scenario + // https://github.com/owncloud/core/blob/v10.4.0/apps/files_sharing/lib/Controller/Share20OcsController.php#L447 + publicUploadString, ok := r.Form["publicUpload"] + if ok { + publicUploadFlag, err := strconv.ParseBool(publicUploadString[0]) + if err != nil { + log.Error().Err(err).Str("publicUpload", publicUploadString[0]).Msg("could not parse publicUpload argument") + return nil, err + } + + if publicUploadFlag { + // all perms except reshare + permKey = 15 + } + } else { + permissionsString, ok := r.Form["permissions"] + if !ok { + // no permission values given + return nil, nil + } + + permKey, err = strconv.Atoi(permissionsString[0]) + if err != nil { + log.Error().Str("permissionFromRequest", "shares").Msgf("invalid type: %T", permKey) + return nil, fmt.Errorf("invalid type: %T", permKey) + } + } + + p, err := ocPublicPermToCs3(permKey, h) + if err != nil { + return nil, err + } + return p, err +} + +// TODO: add mapping for user share permissions to role + +// Maps oc10 public link permissions to roles +var ocPublicPermToRole = map[int]string{ + // Recipients can view and download contents. + 1: "viewer", + // Recipients can view, download, edit, delete and upload contents + 15: "editor", + // Recipients can upload but existing contents are not revealed + 4: "uploader", + // Recipients can view, download and upload contents + 5: "contributor", +} diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go index 54d8f6efec..ad849582a2 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go @@ -32,13 +32,11 @@ import ( "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" - "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" ) func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Request, statInfo *provider.ResourceInfo) { ctx := r.Context() - log := appctx.GetLogger(ctx) c, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { @@ -72,41 +70,38 @@ func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Reque return } - var permissions conversions.Permissions - var role string + var role *conversions.Role pval := r.FormValue("permissions") if pval == "" { // by default only allow read permissions / assign viewer role - permissions = conversions.PermissionRead - role = conversions.RoleViewer + role = conversions.NewViewerRole() } else { pint, err := strconv.Atoi(pval) if err != nil { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "permissions must be an integer", nil) return } - permissions, err = conversions.NewPermissions(pint) + permissions, err := conversions.NewPermissions(pint) if err != nil { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, err.Error(), nil) return } - role = conversions.Permissions2Role(permissions) - } - - var resourcePermissions *provider.ResourcePermissions - resourcePermissions, err = h.map2CS3Permissions(role, permissions) - if err != nil { - log.Warn().Err(err).Msg("unknown role, mapping legacy permissions") - resourcePermissions = asCS3Permissions(permissions, nil) + role = conversions.RoleFromOCSPermissions(permissions) } createShareReq := &ocm.CreateOCMShareRequest{ Opaque: &types.Opaque{ Map: map[string]*types.OpaqueEntry{ + /* TODO extend the spec with role names? + "role": { + Decoder: "plain", + Value: []byte(role.Name), + }, + */ "permissions": { Decoder: "plain", - Value: []byte(strconv.Itoa(int(permissions))), + Value: []byte(strconv.Itoa(int(role.OCSPermissions()))), }, "name": { Decoder: "plain", @@ -121,7 +116,7 @@ func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Reque Id: remoteUserRes.RemoteUser.GetId(), }, Permissions: &ocm.SharePermissions{ - Permissions: resourcePermissions, + Permissions: role.CS3ResourcePermissions(), }, }, RecipientMeshProvider: providerInfoResp.ProviderInfo, diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index c6bdba127c..bbb0516599 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -195,12 +195,11 @@ func (h *Handler) createShare(w http.ResponseWriter, r *http.Request) { return } + // check the share permissions do not extend the user permissions if !h.validatePermissions(w, r, statRes.Info.PermissionSet) { return } - // TODO check the share permissions do not extend the user permissions - switch shareType { case int(conversions.ShareTypeUser): h.createUserShare(w, r, statRes.Info) @@ -217,24 +216,18 @@ func (h *Handler) validatePermissions(w http.ResponseWriter, r *http.Request, st // 1. we start without permissions var reqPermissions conversions.Permissions - var err error reqRole := r.FormValue("role") // the share role overrides the requested permissions - if reqRole != "" && reqRole != conversions.RoleLegacy { - reqPermissions, err = conversions.Role2Permissions(reqRole) - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, err.Error(), nil) - return false - } + if reqRole != "" { + reqPermissions = conversions.RoleFromName(reqRole).OCSPermissions() } else { // map requested permissions pval := r.FormValue("permissions") if pval == "" { // default is read permissions / role viewer - reqPermissions = conversions.PermissionAll - reqRole = conversions.RoleCoowner + reqPermissions = conversions.NewCoownerRole().OCSPermissions() } else { pint, err := strconv.Atoi(pval) if err != nil { @@ -250,11 +243,10 @@ func (h *Handler) validatePermissions(w http.ResponseWriter, r *http.Request, st } return false } - reqRole = conversions.Permissions2Role(reqPermissions) } } - existingPermissions := conversions.ResourcePermissions2Permissions(statPerms) + existingPermissions := conversions.RoleFromResourcePermissions(statPerms).OCSPermissions() if !existingPermissions.Contain(reqPermissions) { response.WriteOCSError(w, r, http.StatusNotFound, "Cannot set the requested share permissions", nil) return false @@ -285,76 +277,6 @@ func (h *Handler) stat(ctx context.Context, path string) (*provider.StatResponse // PublicShareContextName represent cross boundaries context for the name of the public share type PublicShareContextName string -// TODO sort out mapping, this is just a first guess -// TODO use roles to make this configurable -func asCS3Permissions(p conversions.Permissions, rp *provider.ResourcePermissions) *provider.ResourcePermissions { - if rp == nil { - rp = &provider.ResourcePermissions{} - } - - if p.Contain(conversions.PermissionRead) { - rp.ListContainer = true - rp.ListGrants = true - rp.ListFileVersions = true - rp.ListRecycle = true - rp.Stat = true - rp.GetPath = true - rp.GetQuota = true - rp.InitiateFileDownload = true - } - if p.Contain(conversions.PermissionWrite) { - rp.InitiateFileUpload = true - rp.RestoreFileVersion = true - rp.RestoreRecycleItem = true - } - if p.Contain(conversions.PermissionCreate) { - rp.CreateContainer = true - // FIXME permissions mismatch: double check create vs write file - rp.InitiateFileUpload = true - if p.Contain(conversions.PermissionWrite) { - rp.Move = true // TODO move only when create and write? - } - } - if p.Contain(conversions.PermissionDelete) { - rp.Delete = true - rp.PurgeRecycle = true - } - if p.Contain(conversions.PermissionShare) { - rp.AddGrant = true - rp.RemoveGrant = true // TODO when are you able to unshare / delete - rp.UpdateGrant = true - } - return rp -} - -func (h *Handler) map2CS3Permissions(role string, p conversions.Permissions) (*provider.ResourcePermissions, error) { - // TODO replace usage of this method with asCS3Permissions - rp := &provider.ResourcePermissions{ - ListContainer: p.Contain(conversions.PermissionRead), - ListGrants: p.Contain(conversions.PermissionRead), - ListFileVersions: p.Contain(conversions.PermissionRead), - ListRecycle: p.Contain(conversions.PermissionRead), - Stat: p.Contain(conversions.PermissionRead), - GetPath: p.Contain(conversions.PermissionRead), - GetQuota: p.Contain(conversions.PermissionRead), - InitiateFileDownload: p.Contain(conversions.PermissionRead), - - // FIXME: uploader role with only write permission can use InitiateFileUpload, not anything else - Move: p.Contain(conversions.PermissionWrite), - InitiateFileUpload: p.Contain(conversions.PermissionWrite), - CreateContainer: p.Contain(conversions.PermissionCreate), - Delete: p.Contain(conversions.PermissionDelete), - RestoreFileVersion: p.Contain(conversions.PermissionWrite), - RestoreRecycleItem: p.Contain(conversions.PermissionWrite), - PurgeRecycle: p.Contain(conversions.PermissionDelete), - - AddGrant: p.Contain(conversions.PermissionShare), - RemoveGrant: p.Contain(conversions.PermissionShare), // TODO when are you able to unshare / delete - UpdateGrant: p.Contain(conversions.PermissionShare), - } - return rp, nil -} - func (h *Handler) getShare(w http.ResponseWriter, r *http.Request, shareID string) { var share *conversions.ShareData var resourceID *provider.ResourceId @@ -516,7 +438,7 @@ func (h *Handler) updateShare(w http.ResponseWriter, r *http.Request, shareID st Field: &collaboration.UpdateShareRequest_UpdateField_Permissions{ Permissions: &collaboration.SharePermissions{ // this completely overwrites the permissions for this user - Permissions: asCS3Permissions(permissions, nil), + Permissions: conversions.RoleFromOCSPermissions(permissions).CS3ResourcePermissions(), }, }, }, @@ -1088,81 +1010,3 @@ func parseTimestamp(timestampString string) (*types.Timestamp, error) { Nanos: uint32(final % 1000000000), }, nil } - -func ocPublicPermToCs3(permKey int, h *Handler) (*provider.ResourcePermissions, error) { - role, ok := ocPublicPermToRole[permKey] - if !ok { - log.Error().Str("ocPublicPermToCs3", "shares").Msgf("invalid oC permission: %s", role) - return nil, fmt.Errorf("invalid oC permission: %s", role) - } - - perm, err := conversions.NewPermissions(permKey) - if err != nil { - return nil, err - } - - p, err := h.map2CS3Permissions(role, perm) - if err != nil { - log.Error().Str("permissionFromRequest", "shares").Msgf("role to cs3permission %v", perm) - return nil, fmt.Errorf("role to cs3permission failed: %v", perm) - } - - return p, nil -} - -func permissionFromRequest(r *http.Request, h *Handler) (*provider.ResourcePermissions, error) { - var err error - // phoenix sends: {"permissions": 15}. See ocPublicPermToRole struct for mapping - - permKey := 1 - - // note: "permissions" value has higher priority than "publicUpload" - - // handle legacy "publicUpload" arg that overrides permissions differently depending on the scenario - // https://github.com/owncloud/core/blob/v10.4.0/apps/files_sharing/lib/Controller/Share20OcsController.php#L447 - publicUploadString, ok := r.Form["publicUpload"] - if ok { - publicUploadFlag, err := strconv.ParseBool(publicUploadString[0]) - if err != nil { - log.Error().Err(err).Str("publicUpload", publicUploadString[0]).Msg("could not parse publicUpload argument") - return nil, err - } - - if publicUploadFlag { - // all perms except reshare - permKey = 15 - } - } else { - permissionsString, ok := r.Form["permissions"] - if !ok { - // no permission values given - return nil, nil - } - - permKey, err = strconv.Atoi(permissionsString[0]) - if err != nil { - log.Error().Str("permissionFromRequest", "shares").Msgf("invalid type: %T", permKey) - return nil, fmt.Errorf("invalid type: %T", permKey) - } - } - - p, err := ocPublicPermToCs3(permKey, h) - if err != nil { - return nil, err - } - return p, err -} - -// TODO: add mapping for user share permissions to role - -// Maps oc10 public link permissions to roles -var ocPublicPermToRole = map[int]string{ - // Recipients can view and download contents. - 1: "viewer", - // Recipients can view, download, edit, delete and upload contents - 15: "editor", - // Recipients can upload but existing contents are not revealed - 4: "uploader", - // Recipients can view, download and upload contents - 5: "contributor", -} diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go index f5decf20dd..9be7c34b27 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go @@ -43,23 +43,25 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn return } - var permissions conversions.Permissions + var role *conversions.Role - role := r.FormValue("role") - // 2. if we don't have a role try to map the permissions - if role == "" { + reqRole := r.FormValue("role") + if reqRole != "" { + // default is all permissions / role coowner + role = conversions.RoleFromName(reqRole) + } else { + // map requested permissions pval := r.FormValue("permissions") if pval == "" { // default is all permissions / role coowner - permissions = conversions.PermissionAll - role = conversions.RoleCoowner + role = conversions.NewCoownerRole() } else { pint, err := strconv.Atoi(pval) if err != nil { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "permissions must be an integer", nil) return } - permissions, err = conversions.NewPermissions(pint) + permissions, err := conversions.NewPermissions(pint) if err != nil { if err == conversions.ErrPermissionNotInRange { response.WriteOCSError(w, r, http.StatusNotFound, err.Error(), nil) @@ -68,20 +70,20 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn } return } - role = conversions.Permissions2Role(permissions) + role = conversions.RoleFromOCSPermissions(permissions) } } if statInfo != nil && statInfo.Type == provider.ResourceType_RESOURCE_TYPE_FILE { // Single file shares should never have delete or create permissions + permissions := role.OCSPermissions() permissions &^= conversions.PermissionCreate permissions &^= conversions.PermissionDelete + // editor should be come a file-editor role + role = conversions.RoleFromOCSPermissions(permissions) } - var resourcePermissions *provider.ResourcePermissions - resourcePermissions = asCS3Permissions(permissions, resourcePermissions) - - roleMap := map[string]string{"name": role} + roleMap := map[string]string{"name": role.Name} val, err := json.Marshal(roleMap) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "could not encode role", err) @@ -124,7 +126,7 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn Id: userRes.User.GetId(), }, Permissions: &collaboration.SharePermissions{ - Permissions: resourcePermissions, + Permissions: role.CS3ResourcePermissions(), }, }, } From 30af05b7862dfebca3b5a4cd13f2fa5fb8ebefb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 10 Dec 2020 21:59:14 +0000 Subject: [PATCH 03/41] stop faking dav permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../http/services/owncloud/ocdav/propfind.go | 37 +++++++++-- .../services/owncloud/ocs/conversions/role.go | 63 ++++++++++++++++++- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 5756b57a6b..bd7eec2abc 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -33,10 +33,12 @@ import ( "go.opencensus.io/trace" + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/pkg/appctx" + ctxuser "github.com/cs3org/reva/pkg/user" "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" ) @@ -299,8 +301,16 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } if md.PermissionSet != nil { - // TODO(jfd) no longer hardcode permissions - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:permissions", "WCKDNVR")) + response.Propstat[0].Prop = append( + response.Propstat[0].Prop, + s.newProp("oc:permissions", + conversions.RoleFromResourcePermissions(md.PermissionSet). + WebDAVPermissions( + md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, + !isCurrentUserOwner(ctx, md.Owner), + false, + false, + ))) } // always return size @@ -378,8 +388,16 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } case "permissions": // both if md.PermissionSet != nil { - // TODO(jfd): properly build permissions string - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:permissions", "WCKDNVR")) + propstatOK.Prop = append( + propstatOK.Prop, + s.newProp("oc:permissions", + conversions.RoleFromResourcePermissions(md.PermissionSet). + WebDAVPermissions( + md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, + !isCurrentUserOwner(ctx, md.Owner), + false, + false, + ))) } else { propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:permissions", "")) } @@ -521,6 +539,17 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide return &response, nil } +// a file is only yours if you are the owner +func isCurrentUserOwner(ctx context.Context, owner *userv1beta1.UserId) bool { + contextUser, ok := ctxuser.ContextGetUser(ctx) + if ok && contextUser.Id != nil && owner != nil && + contextUser.Id.Idp == owner.Idp && + contextUser.Id.OpaqueId == owner.OpaqueId { + return true + } + return false +} + type countingReader struct { n int r io.Reader diff --git a/internal/http/services/owncloud/ocs/conversions/role.go b/internal/http/services/owncloud/ocs/conversions/role.go index 4a920ae971..a17163a2b1 100644 --- a/internal/http/services/owncloud/ocs/conversions/role.go +++ b/internal/http/services/owncloud/ocs/conversions/role.go @@ -19,7 +19,12 @@ // Package conversions sits between CS3 type definitions and OCS API Responses package conversions -import provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +import ( + "fmt" + "strings" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) // Role describes the interface to transform different permission sets into each other type Role struct { @@ -55,6 +60,62 @@ func (r *Role) OCSPermissions() Permissions { return r.OCSPermissions() } +// WebDAVPermissions returns the webdav permissions used in propfinds, eg. "WCKDNVR" +/* + from https://github.com/owncloud/core/blob/10715e2b1c85fc3855a38d2b1fe4426b5e3efbad/apps/dav/lib/Files/PublicFiles/SharedNodeTrait.php#L196-L215 + + $p = ''; + if ($node->isDeletable() && $this->checkSharePermissions(Constants::PERMISSION_DELETE)) { + $p .= 'D'; + } + if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { + $p .= 'NV'; // Renameable, Moveable + } + if ($node->getType() === \OCP\Files\FileInfo::TYPE_FILE) { + if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { + $p .= 'W'; + } + } else { + if ($node->isCreatable() && $this->checkSharePermissions(Constants::PERMISSION_CREATE)) { + $p .= 'CK'; + } + } + +*/ +// D = delete +// NV = update (renameable moveable) +// W = update (files only) +// CK = create (folders only) +// S = Shared +// R = Shareable +// M = Mounted +func (r *Role) WebDAVPermissions(isDir, isShared, isMountpoint, isPublic bool) string { + var b strings.Builder + //b.Grow(7) + if r.ocsPermissions.Contain(PermissionDelete) { + fmt.Fprintf(&b, "D") + } + if r.ocsPermissions.Contain(PermissionWrite) { + fmt.Fprintf(&b, "NV") + if !isDir { + fmt.Fprintf(&b, "W") + } + } + if isDir && r.ocsPermissions.Contain(PermissionCreate) { + fmt.Fprintf(&b, "CK") + } + if !isPublic && isShared { + fmt.Fprintf(&b, "S") + } + if r.ocsPermissions.Contain(PermissionShare) { + fmt.Fprintf(&b, "R") + } + if !isPublic && isMountpoint { + fmt.Fprintf(&b, "M") + } + return b.String() +} + // RoleFromName creates a role from the name func RoleFromName(name string) *Role { switch name { From 2974e0c85a849958ae0ee90d151aab87a1ac2b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 11 Dec 2020 14:07:58 +0000 Subject: [PATCH 04/41] make ocis driver calculate permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../http/services/owncloud/ocdav/propfind.go | 35 +++--- .../services/owncloud/ocs/conversions/role.go | 2 +- pkg/storage/fs/ocis/lookup.go | 1 + pkg/storage/fs/ocis/node.go | 24 ++-- pkg/storage/fs/ocis/ocis.go | 20 ++- pkg/storage/fs/ocis/permissions.go | 114 ++++++++++++++++++ 6 files changed, 158 insertions(+), 38 deletions(-) diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index bd7eec2abc..423e172817 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -263,6 +263,7 @@ func (s *svc) newProp(key, val string) *propertyXML { // ns is the CS3 namespace that needs to be removed from the CS3 path before // prefixing it with the baseURI func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provider.ResourceInfo, ns string) (*responseXML, error) { + sublog := appctx.GetLogger(ctx).With().Interface("md", md).Str("ns", ns).Logger() md.Path = strings.TrimPrefix(md.Path, ns) baseURI := ctx.Value(ctxKeyBaseURI).(string) @@ -301,16 +302,17 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } if md.PermissionSet != nil { + r := conversions.RoleFromResourcePermissions(md.PermissionSet) + wdp := r.WebDAVPermissions( + md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, + !isCurrentUserOwner(ctx, md.Owner), + false, + false, + ) + sublog.Debug().Interface("role", r).Str("dav-permissions", wdp).Msg("converted PermissionSet") response.Propstat[0].Prop = append( response.Propstat[0].Prop, - s.newProp("oc:permissions", - conversions.RoleFromResourcePermissions(md.PermissionSet). - WebDAVPermissions( - md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, - !isCurrentUserOwner(ctx, md.Owner), - false, - false, - ))) + s.newProp("oc:permissions", wdp)) } // always return size @@ -388,16 +390,17 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } case "permissions": // both if md.PermissionSet != nil { + r := conversions.RoleFromResourcePermissions(md.PermissionSet) + wdp := r.WebDAVPermissions( + md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, + !isCurrentUserOwner(ctx, md.Owner), + false, + false, + ) + sublog.Debug().Interface("role", r).Str("dav-permissions", wdp).Msg("converted PermissionSet") propstatOK.Prop = append( propstatOK.Prop, - s.newProp("oc:permissions", - conversions.RoleFromResourcePermissions(md.PermissionSet). - WebDAVPermissions( - md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, - !isCurrentUserOwner(ctx, md.Owner), - false, - false, - ))) + s.newProp("oc:permissions", wdp)) } else { propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:permissions", "")) } diff --git a/internal/http/services/owncloud/ocs/conversions/role.go b/internal/http/services/owncloud/ocs/conversions/role.go index a17163a2b1..d7caa228f1 100644 --- a/internal/http/services/owncloud/ocs/conversions/role.go +++ b/internal/http/services/owncloud/ocs/conversions/role.go @@ -57,7 +57,7 @@ func (r *Role) CS3ResourcePermissions() *provider.ResourcePermissions { // OCSPermissions for the role func (r *Role) OCSPermissions() Permissions { - return r.OCSPermissions() + return r.ocsPermissions } // WebDAVPermissions returns the webdav permissions used in propfinds, eg. "WCKDNVR" diff --git a/pkg/storage/fs/ocis/lookup.go b/pkg/storage/fs/ocis/lookup.go index 2a189095b1..fdc1166e45 100644 --- a/pkg/storage/fs/ocis/lookup.go +++ b/pkg/storage/fs/ocis/lookup.go @@ -59,6 +59,7 @@ func (lu *Lookup) NodeFromPath(ctx context.Context, fn string) (node *Node, err return } + // TODO collect permissions of the current user on every segment if fn != "/" { node, err = lu.WalkPath(ctx, node, fn, func(ctx context.Context, n *Node) error { log.Debug().Interface("node", n).Msg("NodeFromPath() walk") diff --git a/pkg/storage/fs/ocis/node.go b/pkg/storage/fs/ocis/node.go index d749f99754..d0b46fb471 100644 --- a/pkg/storage/fs/ocis/node.go +++ b/pkg/storage/fs/ocis/node.go @@ -279,6 +279,10 @@ func (n *Node) Owner() (id string, idp string, err error) { return n.ownerID, n.ownerIDP, nil } + // FIXME ... do we return the owner of the reference or the owner of the target? + // we don't really know the owner of the target ... and as the reference may point anywhere we cannot really find out + // but what are the permissions? all? none? the gateway has to fill in? + // TODO what if this is a reference? nodePath := n.lu.toInternalPath(n.ID) // lookup parent id in extended attributes var attrBytes []byte @@ -297,13 +301,14 @@ func (n *Node) Owner() (id string, idp string, err error) { return n.ownerID, n.ownerIDP, err } -// PermissionSet returns the permission set for the curren user +// PermissionSet returns the permission set for the current user func (n *Node) PermissionSet(ctx context.Context) *provider.ResourcePermissions { u, ok := user.ContextGetUser(ctx) if !ok { appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("no user in context, returning default permissions") return defaultPermissions } + n.Owner() // read owner if u.Id.OpaqueId == n.ownerID && u.Id.Idp == n.ownerIDP { return ownerPermissions } @@ -317,7 +322,7 @@ func (n *Node) PermissionSet(ctx context.Context) *provider.ResourcePermissions } // AsResourceInfo return the node as CS3 ResourceInfo -func (n *Node) AsResourceInfo(ctx context.Context, mdKeys []string) (ri *provider.ResourceInfo, err error) { +func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissions, mdKeys []string) (ri *provider.ResourceInfo, err error) { log := appctx.GetLogger(ctx) var fn string @@ -354,12 +359,13 @@ func (n *Node) AsResourceInfo(ctx context.Context, mdKeys []string) (ri *provide } ri = &provider.ResourceInfo{ - Id: id, - Path: fn, - Type: nodeType, - MimeType: mime.Detect(nodeType == provider.ResourceType_RESOURCE_TYPE_CONTAINER, fn), - Size: uint64(fi.Size()), - Target: string(target), + Id: id, + Path: fn, + Type: nodeType, + MimeType: mime.Detect(nodeType == provider.ResourceType_RESOURCE_TYPE_CONTAINER, fn), + Size: uint64(fi.Size()), + Target: string(target), + PermissionSet: rp, } if owner, idp, err := n.Owner(); err == nil { @@ -369,8 +375,6 @@ func (n *Node) AsResourceInfo(ctx context.Context, mdKeys []string) (ri *provide } } - ri.PermissionSet = n.PermissionSet(ctx) - // etag currently is a hash of fileid + tmtime (or mtime) // TODO make etag of files use fileid and checksum // TODO implment adding temporery etag in an attribute to restore backups diff --git a/pkg/storage/fs/ocis/ocis.go b/pkg/storage/fs/ocis/ocis.go index 2cb9d2be10..fd68e4484f 100644 --- a/pkg/storage/fs/ocis/ocis.go +++ b/pkg/storage/fs/ocis/ocis.go @@ -360,18 +360,15 @@ func (fs *ocisfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []s err = errtypes.NotFound(filepath.Join(node.ParentID, node.Name)) return } - - ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool { - return rp.Stat - }) + rp, err := fs.p.ReadUserPermissions(ctx, node) switch { case err != nil: return nil, errtypes.InternalError(err.Error()) - case !ok: + case !rp.Stat: return nil, errtypes.PermissionDenied(node.ID) } - return node.AsResourceInfo(ctx, mdKeys) + return node.AsResourceInfo(ctx, rp, mdKeys) } func (fs *ocisfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) (finfos []*provider.ResourceInfo, err error) { @@ -385,13 +382,11 @@ func (fs *ocisfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKey return } - ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool { - return rp.ListContainer - }) + rp, err := fs.p.ReadUserPermissions(ctx, node) switch { case err != nil: return nil, errtypes.InternalError(err.Error()) - case !ok: + case !rp.ListContainer: return nil, errtypes.PermissionDenied(node.ID) } @@ -402,7 +397,10 @@ func (fs *ocisfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKey } for i := range children { - if ri, err := children[i].AsResourceInfo(ctx, mdKeys); err == nil { + np := rp + addPermissions(np, node.PermissionSet(ctx)) + // TODO only add this childs permissions + if ri, err := children[i].AsResourceInfo(ctx, np, mdKeys); err == nil { finfos = append(finfos, ri) } } diff --git a/pkg/storage/fs/ocis/permissions.go b/pkg/storage/fs/ocis/permissions.go index d74c0f4f31..2612162163 100644 --- a/pkg/storage/fs/ocis/permissions.go +++ b/pkg/storage/fs/ocis/permissions.go @@ -71,6 +71,120 @@ type Permissions struct { lu *Lookup } +// ReadUserPermissions will assemble the permissions for the current user on the given node +func (p *Permissions) ReadUserPermissions(ctx context.Context, n *Node) (ap *provider.ResourcePermissions, err error) { + u, ok := user.ContextGetUser(ctx) + if !ok { + appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("no user in context, returning default permissions") + return defaultPermissions, nil + } + // check if the current user is the owner + id, idp, err := n.Owner() + if err != nil { + // TODO check if a parent folder has the owner set? + appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not determine owner, returning default permissions") + return defaultPermissions, err + } + if id == "" { + // TODO what if no owner is set but grants are present? + return noOwnerPermissions, nil + } + if id == u.Id.OpaqueId && idp == u.Id.Idp { + appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("user is owner, returning owner permissions") + return ownerPermissions, nil + } + + // determine root + var rn *Node + if rn, err = p.lu.RootNode(ctx); err != nil { + return nil, err + } + + cn := n + + ap = &provider.ResourcePermissions{} + + // for an efficient group lookup convert the list of groups to a map + // groups are just strings ... groupnames ... or group ids ??? AAARGH !!! + groupsMap := make(map[string]bool, len(u.Groups)) + for i := range u.Groups { + groupsMap[u.Groups[i]] = true + } + + var g *provider.Grant + // for all segments, starting at the leaf + for cn.ID != rn.ID { + + var grantees []string + if grantees, err = cn.ListGrantees(ctx); err != nil { + appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn).Msg("error listing grantees") + return nil, err + } + + userace := grantPrefix + _userAcePrefix + u.Id.OpaqueId + userFound := false + for i := range grantees { + // we only need the find the user once per node + switch { + case !userFound && grantees[i] == userace: + g, err = cn.ReadGrant(ctx, grantees[i]) + case strings.HasPrefix(grantees[i], grantPrefix+_groupAcePrefix): + gr := strings.TrimPrefix(grantees[i], grantPrefix+_groupAcePrefix) + if groupsMap[gr] { + g, err = cn.ReadGrant(ctx, grantees[i]) + } else { + // no need to check attribute + continue + } + default: + // no need to check attribute + continue + } + + switch { + case err == nil: + addPermissions(ap, g.GetPermissions()) + case isNoData(err): + err = nil + appctx.GetLogger(ctx).Error().Interface("node", cn).Str("grant", grantees[i]).Interface("grantees", grantees).Msg("grant vanished from node after listing") + // continue with next segment + default: + appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn).Str("grant", grantees[i]).Msg("error reading permissions") + // continue with next segment + } + } + + if cn, err = cn.Parent(); err != nil { + return ap, errors.Wrap(err, "ocisfs: error getting parent "+cn.ParentID) + } + } + + appctx.GetLogger(ctx).Debug().Interface("permissions", ap).Interface("node", n).Interface("user", u).Msg("returning agregated permissions") + return ap, nil +} + +// TODO we should use a bitfield for this ... +func addPermissions(l *provider.ResourcePermissions, r *provider.ResourcePermissions) { + l.AddGrant = l.AddGrant || r.AddGrant + l.CreateContainer = l.CreateContainer || r.CreateContainer + l.Delete = l.Delete || r.Delete + l.GetPath = l.GetPath || r.GetPath + l.GetQuota = l.GetQuota || r.GetQuota + l.InitiateFileDownload = l.InitiateFileDownload || r.InitiateFileDownload + l.InitiateFileUpload = l.InitiateFileUpload || r.InitiateFileUpload + l.ListContainer = l.ListContainer || r.ListContainer + l.ListFileVersions = l.ListFileVersions || r.ListFileVersions + l.ListGrants = l.ListGrants || r.ListGrants + l.ListRecycle = l.ListRecycle || r.ListRecycle + l.Move = l.Move || r.Move + l.PurgeRecycle = l.PurgeRecycle || r.PurgeRecycle + l.RemoveGrant = l.RemoveGrant || r.RemoveGrant + l.RestoreFileVersion = l.RestoreFileVersion || r.RestoreFileVersion + l.RestoreRecycleItem = l.RestoreRecycleItem || r.RestoreRecycleItem + l.Stat = l.Stat || r.Stat + l.UpdateGrant = l.UpdateGrant || r.UpdateGrant +} + // HasPermission call check() for every node up to the root until check returns true func (p *Permissions) HasPermission(ctx context.Context, n *Node, check func(*provider.ResourcePermissions) bool) (can bool, err error) { From d0c6fdcefcca96d98c19150754423323854c3309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 11 Dec 2020 15:19:20 +0000 Subject: [PATCH 05/41] return all permissions when user is owner wherever possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- pkg/storage/fs/ocis/ocis.go | 2 +- pkg/storage/fs/owncloud/owncloud.go | 62 ++++++++++++++++++++++++---- pkg/storage/fs/s3/s3.go | 48 ++++++++++++++++----- pkg/storage/utils/eosfs/eosfs.go | 6 +-- pkg/storage/utils/localfs/localfs.go | 45 +++++++++++++++++++- 5 files changed, 139 insertions(+), 24 deletions(-) diff --git a/pkg/storage/fs/ocis/ocis.go b/pkg/storage/fs/ocis/ocis.go index fd68e4484f..a01ad08c9e 100644 --- a/pkg/storage/fs/ocis/ocis.go +++ b/pkg/storage/fs/ocis/ocis.go @@ -398,8 +398,8 @@ func (fs *ocisfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKey for i := range children { np := rp + // add this childs permissions addPermissions(np, node.PermissionSet(ctx)) - // TODO only add this childs permissions if ri, err := children[i].AsResourceInfo(ctx, np, mdKeys); err == nil { finfos = append(finfos, ri) } diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index 22fb636eb5..1a08f1b3e0 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -523,6 +523,53 @@ func (fs *ocfs) getUser(ctx context.Context, usernameOrID string) (id *userpb.Us return res.User, nil } +// permissionSet returns the permission set for the current user +func (fs *ocfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provider.ResourcePermissions { + if owner == nil { + return &provider.ResourcePermissions{ + Stat: true, + } + } + u, ok := user.ContextGetUser(ctx) + if !ok { + return &provider.ResourcePermissions{ + // no permissions + } + } + if u.Id == nil { + return &provider.ResourcePermissions{ + // no permissions + } + } + if u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp { + return &provider.ResourcePermissions{ + // owner has all permissions + AddGrant: true, + CreateContainer: true, + Delete: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + InitiateFileUpload: true, + ListContainer: true, + ListFileVersions: true, + ListGrants: true, + ListRecycle: true, + Move: true, + PurgeRecycle: true, + RemoveGrant: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + Stat: true, + UpdateGrant: true, + } + } + // TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it + return &provider.ResourcePermissions{ + ListContainer: true, + CreateContainer: true, + } +} func (fs *ocfs) convertToResourceInfo(ctx context.Context, fi os.FileInfo, ip string, sp string, c redis.Conn, mdKeys []string) *provider.ResourceInfo { id := readOrCreateID(ctx, ip, c) @@ -596,13 +643,12 @@ func (fs *ocfs) convertToResourceInfo(ctx context.Context, fi os.FileInfo, ip st } ri := &provider.ResourceInfo{ - Id: &provider.ResourceId{OpaqueId: id}, - Path: sp, - Type: getResourceType(fi.IsDir()), - Etag: etag, - MimeType: mime.Detect(fi.IsDir(), ip), - Size: uint64(fi.Size()), - PermissionSet: &provider.ResourcePermissions{ListContainer: true, CreateContainer: true}, + Id: &provider.ResourceId{OpaqueId: id}, + Path: sp, + Type: getResourceType(fi.IsDir()), + Etag: etag, + MimeType: mime.Detect(fi.IsDir(), ip), + Size: uint64(fi.Size()), Mtime: &types.Timestamp{ Seconds: uint64(fi.ModTime().Unix()), // TODO read nanos from where? Nanos: fi.MTimeNanos, @@ -618,6 +664,8 @@ func (fs *ocfs) convertToResourceInfo(ctx context.Context, fi os.FileInfo, ip st appctx.GetLogger(ctx).Error().Err(err).Msg("error getting owner") } + ri.PermissionSet = fs.permissionSet(ctx, ri.Owner) + return ri } func getResourceType(isDir bool) provider.ResourceType { diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index a943fdb948..078a9efea2 100644 --- a/pkg/storage/fs/s3/s3.go +++ b/pkg/storage/fs/s3/s3.go @@ -142,6 +142,32 @@ type s3FS struct { config *config } +// permissionSet returns the permission set for the current user +func (fs *s3FS) permissionSet(ctx context.Context) *provider.ResourcePermissions { + // TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it + return &provider.ResourcePermissions{ + // owner has all permissions + AddGrant: true, + CreateContainer: true, + Delete: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + InitiateFileUpload: true, + ListContainer: true, + ListFileVersions: true, + ListGrants: true, + ListRecycle: true, + Move: true, + PurgeRecycle: true, + RemoveGrant: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + Stat: true, + UpdateGrant: true, + } +} + func (fs *s3FS) normalizeObject(ctx context.Context, o *s3.Object, fn string) *provider.ResourceInfo { fn = fs.removeRoot(path.Join("/", fn)) isDir := strings.HasSuffix(*o.Key, "/") @@ -151,7 +177,7 @@ func (fs *s3FS) normalizeObject(ctx context.Context, o *s3.Object, fn string) *p Type: getResourceType(isDir), Etag: *o.ETag, MimeType: mime.Detect(isDir, fn), - PermissionSet: &provider.ResourcePermissions{ListContainer: true, CreateContainer: true}, + PermissionSet: fs.permissionSet(ctx), Size: uint64(*o.Size), Mtime: &types.Timestamp{ Seconds: uint64(o.LastModified.Unix()), @@ -180,7 +206,7 @@ func (fs *s3FS) normalizeHead(ctx context.Context, o *s3.HeadObjectOutput, fn st Type: getResourceType(isDir), Etag: *o.ETag, MimeType: mime.Detect(isDir, fn), - PermissionSet: &provider.ResourcePermissions{ListContainer: true, CreateContainer: true}, + PermissionSet: fs.permissionSet(ctx), Size: uint64(*o.ContentLength), Mtime: &types.Timestamp{ Seconds: uint64(o.LastModified.Unix()), @@ -200,7 +226,7 @@ func (fs *s3FS) normalizeCommonPrefix(ctx context.Context, p *s3.CommonPrefix) * Type: getResourceType(true), Etag: "TODO(labkode)", MimeType: mime.Detect(true, fn), - PermissionSet: &provider.ResourcePermissions{ListContainer: true, CreateContainer: true}, + PermissionSet: fs.permissionSet(ctx), Size: 0, Mtime: &types.Timestamp{ Seconds: 0, @@ -492,13 +518,13 @@ func (fs *s3FS) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []str return nil, errors.Wrap(err, "s3FS: error listing "+fn) } - for _, o := range output.CommonPrefixes { + for i := range output.CommonPrefixes { log.Debug(). - Interface("object", *o). + Interface("object", output.CommonPrefixes[i]). Str("fn", fn). Msg("found CommonPrefix") - if *o.Prefix == fn+"/" { - return fs.normalizeCommonPrefix(ctx, o), nil + if *output.CommonPrefixes[i].Prefix == fn+"/" { + return fs.normalizeCommonPrefix(ctx, output.CommonPrefixes[i]), nil } } @@ -532,12 +558,12 @@ func (fs *s3FS) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys return nil, errors.Wrap(err, "s3FS: error listing "+fn) } - for _, p := range output.CommonPrefixes { - finfos = append(finfos, fs.normalizeCommonPrefix(ctx, p)) + for i := range output.CommonPrefixes { + finfos = append(finfos, fs.normalizeCommonPrefix(ctx, output.CommonPrefixes[i])) } - for _, o := range output.Contents { - finfos = append(finfos, fs.normalizeObject(ctx, o, *o.Key)) + for i := range output.Contents { + finfos = append(finfos, fs.normalizeObject(ctx, output.Contents[i], *output.Contents[i].Key)) } input.ContinuationToken = output.NextContinuationToken diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 8952f68489..87f53a3022 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1310,24 +1310,22 @@ func (fs *eosfs) convertToFileReference(ctx context.Context, eosFileInfo *eoscli return info, nil } -// permissionSet returns the permission set for the curren user +// permissionSet returns the permission set for the current user func (fs *eosfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provider.ResourcePermissions { u, ok := user.ContextGetUser(ctx) if !ok { - appctx.GetLogger(ctx).Debug().Msg("no user in context, returning default permissions (none)") return &provider.ResourcePermissions{ // no permissions } } if u.Id == nil { - appctx.GetLogger(ctx).Error().Msg("user has no id, returning default permissions (none)") return &provider.ResourcePermissions{ // no permissions } } if u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp { return &provider.ResourcePermissions{ - // owner has all all permissions + // owner has all permissions AddGrant: true, CreateContainer: true, Delete: true, diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index 909b1acd9d..f49016e71f 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -252,6 +252,49 @@ func (fs *localfs) isShareFolderChild(ctx context.Context, p string) bool { return len(vals) > 1 && vals[1] != "" } +// permissionSet returns the permission set for the current user +func (fs *localfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provider.ResourcePermissions { + u, ok := user.ContextGetUser(ctx) + if !ok { + return &provider.ResourcePermissions{ + // no permissions + } + } + if u.Id == nil { + return &provider.ResourcePermissions{ + // no permissions + } + } + if u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp { + return &provider.ResourcePermissions{ + // owner has all permissions + AddGrant: true, + CreateContainer: true, + Delete: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + InitiateFileUpload: true, + ListContainer: true, + ListFileVersions: true, + ListGrants: true, + ListRecycle: true, + Move: true, + PurgeRecycle: true, + RemoveGrant: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + Stat: true, + UpdateGrant: true, + } + } + // TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it + return &provider.ResourcePermissions{ + ListContainer: true, + CreateContainer: true, + } +} + func (fs *localfs) normalize(ctx context.Context, fi os.FileInfo, fn string, mdKeys []string) (*provider.ResourceInfo, error) { fp := fs.unwrap(ctx, path.Join("/", fn)) owner, err := getUser(ctx) @@ -279,7 +322,7 @@ func (fs *localfs) normalize(ctx context.Context, fi os.FileInfo, fn string, mdK Etag: calcEtag(ctx, fi), MimeType: mime.Detect(fi.IsDir(), fp), Size: uint64(fi.Size()), - PermissionSet: &provider.ResourcePermissions{ListContainer: true, CreateContainer: true}, + PermissionSet: fs.permissionSet(ctx, owner.Id), Mtime: &types.Timestamp{ Seconds: uint64(fi.ModTime().Unix()), }, From c8283b060cd9e6e911e1294d00ca31859a75b1ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 14 Dec 2020 15:07:22 +0000 Subject: [PATCH 06/41] refactor reading and assembling permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- pkg/storage/fs/ocis/node.go | 86 ++++++++++++++++++++++++++++-- pkg/storage/fs/ocis/ocis.go | 5 +- pkg/storage/fs/ocis/permissions.go | 59 +++++--------------- pkg/storage/fs/ocis/recycle.go | 3 +- 4 files changed, 100 insertions(+), 53 deletions(-) diff --git a/pkg/storage/fs/ocis/node.go b/pkg/storage/fs/ocis/node.go index d0b46fb471..3f2200247f 100644 --- a/pkg/storage/fs/ocis/node.go +++ b/pkg/storage/fs/ocis/node.go @@ -306,7 +306,7 @@ func (n *Node) PermissionSet(ctx context.Context) *provider.ResourcePermissions u, ok := user.ContextGetUser(ctx) if !ok { appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("no user in context, returning default permissions") - return defaultPermissions + return noPermissions } n.Owner() // read owner if u.Id.OpaqueId == n.ownerID && u.Id.Idp == n.ownerIDP { @@ -315,10 +315,10 @@ func (n *Node) PermissionSet(ctx context.Context) *provider.ResourcePermissions // TODO fix permissions for share recipients by traversing up to the next acl? or to the root? // ouch ... if we have to read acls for every dir entry that would be n*depth additional xattr reads ... // but we only need to read the parent tree once. - return &provider.ResourcePermissions{ - ListContainer: true, - CreateContainer: true, + if np, err := n.ReadUserPermissions(ctx, u); err == nil { + return np } + return noPermissions } // AsResourceInfo return the node as CS3 ResourceInfo @@ -476,6 +476,84 @@ func (n *Node) UnsetTempEtag() (err error) { return err } +// ReadUserPermissions will assemble the permissions for the current user on the given node without parent nodes +func (n *Node) ReadUserPermissions(ctx context.Context, u *userpb.User) (ap *provider.ResourcePermissions, err error) { + // check if the current user is the owner + id, idp, err := n.Owner() + if err != nil { + // TODO check if a parent folder has the owner set? + appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not determine owner, returning default permissions") + return noPermissions, err + } + if id == "" { + // TODO what if no owner is set but grants are present? + return noOwnerPermissions, nil + } + if id == u.Id.OpaqueId && idp == u.Id.Idp { + appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("user is owner, returning owner permissions") + return ownerPermissions, nil + } + + ap = &provider.ResourcePermissions{} + + // for an efficient group lookup convert the list of groups to a map + // groups are just strings ... groupnames ... or group ids ??? AAARGH !!! + groupsMap := make(map[string]bool, len(u.Groups)) + for i := range u.Groups { + groupsMap[u.Groups[i]] = true + } + + var g *provider.Grant + + // we read all grantees from the node + var grantees []string + if grantees, err = n.ListGrantees(ctx); err != nil { + appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("error listing grantees") + return nil, err + } + + // instead of making n getxattr syscalls we are going to list the acls and filter them here + // we have two options here: + // 1. we can start iterating over the acls / grants on the node or + // 2. we can iterate over the number of groups + // The current implementation tries to be defensive for cases where users have hundreds or thousants of groups, so we iterate over the existing acls. + userace := grantPrefix + _userAcePrefix + u.Id.OpaqueId + userFound := false + for i := range grantees { + switch { + // we only need to find the user once + case !userFound && grantees[i] == userace: + g, err = n.ReadGrant(ctx, grantees[i]) + case strings.HasPrefix(grantees[i], grantPrefix+_groupAcePrefix): // only check group grantees + gr := strings.TrimPrefix(grantees[i], grantPrefix+_groupAcePrefix) + if groupsMap[gr] { + g, err = n.ReadGrant(ctx, grantees[i]) + } else { + // no need to check attribute + continue + } + default: + // no need to check attribute + continue + } + + switch { + case err == nil: + addPermissions(ap, g.GetPermissions()) + case isNoData(err): + err = nil + appctx.GetLogger(ctx).Error().Interface("node", n).Str("grant", grantees[i]).Interface("grantees", grantees).Msg("grant vanished from node after listing") + // continue with next segment + default: + appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Str("grant", grantees[i]).Msg("error reading permissions") + // continue with next segment + } + } + + appctx.GetLogger(ctx).Debug().Interface("permissions", ap).Interface("node", n).Interface("user", u).Msg("returning aggregated permissions") + return ap, nil +} + // ListGrantees lists the grantees of the current node // We don't want to wast time and memory by creating grantee objects. // The function will return a list of opaque strings that can be used to make a ReadGrant call diff --git a/pkg/storage/fs/ocis/ocis.go b/pkg/storage/fs/ocis/ocis.go index a01ad08c9e..eda014e94c 100644 --- a/pkg/storage/fs/ocis/ocis.go +++ b/pkg/storage/fs/ocis/ocis.go @@ -360,7 +360,8 @@ func (fs *ocisfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []s err = errtypes.NotFound(filepath.Join(node.ParentID, node.Name)) return } - rp, err := fs.p.ReadUserPermissions(ctx, node) + + rp, err := fs.p.AssemblePermissions(ctx, node) switch { case err != nil: return nil, errtypes.InternalError(err.Error()) @@ -382,7 +383,7 @@ func (fs *ocisfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKey return } - rp, err := fs.p.ReadUserPermissions(ctx, node) + rp, err := fs.p.AssemblePermissions(ctx, node) switch { case err != nil: return nil, errtypes.InternalError(err.Error()) diff --git a/pkg/storage/fs/ocis/permissions.go b/pkg/storage/fs/ocis/permissions.go index 2612162163..f96cae9068 100644 --- a/pkg/storage/fs/ocis/permissions.go +++ b/pkg/storage/fs/ocis/permissions.go @@ -36,7 +36,7 @@ const ( _groupAcePrefix = "g:" ) -var defaultPermissions *provider.ResourcePermissions = &provider.ResourcePermissions{ +var noPermissions *provider.ResourcePermissions = &provider.ResourcePermissions{ // no permissions } @@ -71,19 +71,19 @@ type Permissions struct { lu *Lookup } -// ReadUserPermissions will assemble the permissions for the current user on the given node -func (p *Permissions) ReadUserPermissions(ctx context.Context, n *Node) (ap *provider.ResourcePermissions, err error) { +// AssemblePermissions will assemble the permissions for the current user on the given node, taking into account all parent nodes +func (p *Permissions) AssemblePermissions(ctx context.Context, n *Node) (ap *provider.ResourcePermissions, err error) { u, ok := user.ContextGetUser(ctx) if !ok { appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("no user in context, returning default permissions") - return defaultPermissions, nil + return noPermissions, nil } // check if the current user is the owner id, idp, err := n.Owner() if err != nil { // TODO check if a parent folder has the owner set? appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not determine owner, returning default permissions") - return defaultPermissions, err + return noPermissions, err } if id == "" { // TODO what if no owner is set but grants are present? @@ -111,47 +111,14 @@ func (p *Permissions) ReadUserPermissions(ctx context.Context, n *Node) (ap *pro groupsMap[u.Groups[i]] = true } - var g *provider.Grant // for all segments, starting at the leaf for cn.ID != rn.ID { - var grantees []string - if grantees, err = cn.ListGrantees(ctx); err != nil { - appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn).Msg("error listing grantees") - return nil, err - } - - userace := grantPrefix + _userAcePrefix + u.Id.OpaqueId - userFound := false - for i := range grantees { - // we only need the find the user once per node - switch { - case !userFound && grantees[i] == userace: - g, err = cn.ReadGrant(ctx, grantees[i]) - case strings.HasPrefix(grantees[i], grantPrefix+_groupAcePrefix): - gr := strings.TrimPrefix(grantees[i], grantPrefix+_groupAcePrefix) - if groupsMap[gr] { - g, err = cn.ReadGrant(ctx, grantees[i]) - } else { - // no need to check attribute - continue - } - default: - // no need to check attribute - continue - } - - switch { - case err == nil: - addPermissions(ap, g.GetPermissions()) - case isNoData(err): - err = nil - appctx.GetLogger(ctx).Error().Interface("node", cn).Str("grant", grantees[i]).Interface("grantees", grantees).Msg("grant vanished from node after listing") - // continue with next segment - default: - appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn).Str("grant", grantees[i]).Msg("error reading permissions") - // continue with next segment - } + if np, err := cn.ReadUserPermissions(ctx, u); err == nil { + addPermissions(ap, np) + } else { + appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn).Msg("error reading permissions") + // continue with next segment } if cn, err = cn.Parent(); err != nil { @@ -259,7 +226,7 @@ func (p *Permissions) HasPermission(ctx context.Context, n *Node, check func(*pr } } - appctx.GetLogger(ctx).Debug().Interface("permissions", defaultPermissions).Interface("node", n).Interface("user", u).Msg("no grant found, returning default permissions") + appctx.GetLogger(ctx).Debug().Interface("permissions", noPermissions).Interface("node", n).Interface("user", u).Msg("no grant found, returning default permissions") return false, nil } @@ -267,13 +234,13 @@ func (p *Permissions) getUserAndPermissions(ctx context.Context, n *Node) (*user u, ok := user.ContextGetUser(ctx) if !ok { appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("no user in context, returning default permissions") - return nil, defaultPermissions + return nil, noPermissions } // check if the current user is the owner id, _, err := n.Owner() if err != nil { appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not determine owner, returning default permissions") - return nil, defaultPermissions + return nil, noPermissions } if id == "" { // TODO what if no owner is set but grants are present? diff --git a/pkg/storage/fs/ocis/recycle.go b/pkg/storage/fs/ocis/recycle.go index 63cef3aa18..577298cf08 100644 --- a/pkg/storage/fs/ocis/recycle.go +++ b/pkg/storage/fs/ocis/recycle.go @@ -50,13 +50,14 @@ func (fs *ocisfs) ListRecycle(ctx context.Context) (items []*provider.RecycleIte items = make([]*provider.RecycleItem, 0) // TODO how do we check if the storage allows listing the recycle for the current user? check owner of the root of the storage? + // use permissions ReadUserPermissions? if fs.o.EnableHome { if !ownerPermissions.ListContainer { log.Debug().Msg("owner not allowed to list trash") return items, errtypes.PermissionDenied("owner not allowed to list trash") } } else { - if !defaultPermissions.ListContainer { + if !noPermissions.ListContainer { log.Debug().Msg("default permissions prevent listing trash") return items, errtypes.PermissionDenied("default permissions prevent listing trash") } From ef0c8f4d45fc280c79d20c2967273252872b2109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 14 Dec 2020 15:22:02 +0000 Subject: [PATCH 07/41] fake permissions for share recipients at the driver level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- pkg/storage/fs/owncloud/owncloud.go | 20 ++++++++++++++++++-- pkg/storage/utils/eosfs/eosfs.go | 20 ++++++++++++++++++-- pkg/storage/utils/localfs/localfs.go | 20 ++++++++++++++++++-- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index 1a08f1b3e0..15f0aff408 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -566,8 +566,24 @@ func (fs *ocfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provid } // TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it return &provider.ResourcePermissions{ - ListContainer: true, - CreateContainer: true, + AddGrant: true, + CreateContainer: true, + Delete: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + InitiateFileUpload: true, + ListContainer: true, + ListFileVersions: true, + ListGrants: true, + ListRecycle: true, + Move: true, + PurgeRecycle: true, + RemoveGrant: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + Stat: true, + UpdateGrant: true, } } func (fs *ocfs) convertToResourceInfo(ctx context.Context, fi os.FileInfo, ip string, sp string, c redis.Conn, mdKeys []string) *provider.ResourceInfo { diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 87f53a3022..f83ea47b0a 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1348,8 +1348,24 @@ func (fs *eosfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provi } // TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it return &provider.ResourcePermissions{ - ListContainer: true, - CreateContainer: true, + AddGrant: true, + CreateContainer: true, + Delete: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + InitiateFileUpload: true, + ListContainer: true, + ListFileVersions: true, + ListGrants: true, + ListRecycle: true, + Move: true, + PurgeRecycle: true, + RemoveGrant: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + Stat: true, + UpdateGrant: true, } } diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index f49016e71f..ee7d5ffd4e 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -290,8 +290,24 @@ func (fs *localfs) permissionSet(ctx context.Context, owner *userpb.UserId) *pro } // TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it return &provider.ResourcePermissions{ - ListContainer: true, - CreateContainer: true, + AddGrant: true, + CreateContainer: true, + Delete: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + InitiateFileUpload: true, + ListContainer: true, + ListFileVersions: true, + ListGrants: true, + ListRecycle: true, + Move: true, + PurgeRecycle: true, + RemoveGrant: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + Stat: true, + UpdateGrant: true, } } From 8de73c2eecd8e98a409e3aec147ce2b861052120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 16 Dec 2020 09:08:17 +0000 Subject: [PATCH 08/41] fix permission conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- internal/http/services/owncloud/ocs/conversions/role.go | 4 ++-- .../owncloud/ocs/handlers/apps/sharing/shares/shares.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/http/services/owncloud/ocs/conversions/role.go b/internal/http/services/owncloud/ocs/conversions/role.go index d7caa228f1..c5a3fc7d3e 100644 --- a/internal/http/services/owncloud/ocs/conversions/role.go +++ b/internal/http/services/owncloud/ocs/conversions/role.go @@ -280,7 +280,7 @@ func RoleFromOCSPermissions(p Permissions) *Role { role := "" if p.Contain(PermissionRead) { role = RoleViewer - if p.Contain(PermissionWrite | PermissionCreate | PermissionDelete) { + if p.Contain(PermissionWrite) && p.Contain(PermissionCreate) && p.Contain(PermissionDelete) { role = RoleEditor if p.Contain(PermissionShare) { role = RoleCoowner @@ -374,7 +374,7 @@ func RoleFromResourcePermissions(rp *provider.ResourcePermissions) *Role { r.ocsPermissions = r.ocsPermissions | PermissionShare } if r.ocsPermissions.Contain(PermissionRead) { - if r.ocsPermissions.Contain(PermissionWrite | PermissionCreate | PermissionDelete) { + if r.ocsPermissions.Contain(PermissionWrite) && r.ocsPermissions.Contain(PermissionCreate) && r.ocsPermissions.Contain(PermissionDelete) { r.Name = RoleEditor if r.ocsPermissions.Contain(PermissionShare) { r.Name = RoleCoowner diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index bbb0516599..3e1672b78c 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -155,7 +155,7 @@ func (h *Handler) createShare(w http.ResponseWriter, r *http.Request) { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "shareType must be an integer", nil) return } - // TODO get user permissions on the shared file + // get user permissions on the shared file c, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { From bedbc4ea3483ea34d4b6302f049100417eec10f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 16 Dec 2020 09:57:31 +0000 Subject: [PATCH 09/41] fix lint, less expected failed tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../services/owncloud/ocs/conversions/role.go | 18 +++--- .../handlers/apps/sharing/shares/shares.go | 20 ------- pkg/storage/fs/ocis/node.go | 59 ++++++++++--------- pkg/storage/fs/ocis/ocis.go | 12 ++++ pkg/storage/fs/ocis/permissions.go | 14 +++-- pkg/storage/fs/ocis/tree.go | 10 ++-- ...erations-getWebDAVSharePermissions.feature | 23 -------- 7 files changed, 64 insertions(+), 92 deletions(-) delete mode 100644 tests/acceptance/features/apiOcisSpecific/apiShareOperations-getWebDAVSharePermissions.feature diff --git a/internal/http/services/owncloud/ocs/conversions/role.go b/internal/http/services/owncloud/ocs/conversions/role.go index c5a3fc7d3e..347b915f2f 100644 --- a/internal/http/services/owncloud/ocs/conversions/role.go +++ b/internal/http/services/owncloud/ocs/conversions/role.go @@ -277,21 +277,17 @@ func NewUploaderRole() *Role { // RoleFromOCSPermissions tries to map ocs permissions to a role func RoleFromOCSPermissions(p Permissions) *Role { - role := "" if p.Contain(PermissionRead) { - role = RoleViewer if p.Contain(PermissionWrite) && p.Contain(PermissionCreate) && p.Contain(PermissionDelete) { - role = RoleEditor if p.Contain(PermissionShare) { - role = RoleCoowner + return NewCoownerRole() } - return RoleFromName(role) // editor or coowner + return NewEditorRole() } if p == PermissionRead { return NewViewerRole() } } - role = RoleLegacy // legacy return NewLegacyRoleFromOCSPermissions(p) } @@ -353,25 +349,25 @@ func RoleFromResourcePermissions(rp *provider.ResourcePermissions) *Role { rp.GetPath && rp.GetQuota && rp.InitiateFileDownload { - r.ocsPermissions = r.ocsPermissions | PermissionRead + r.ocsPermissions |= PermissionRead } if rp.InitiateFileUpload && rp.RestoreFileVersion && rp.RestoreRecycleItem { - r.ocsPermissions = r.ocsPermissions | PermissionWrite + r.ocsPermissions |= PermissionWrite } if rp.CreateContainer && rp.InitiateFileUpload { - r.ocsPermissions = r.ocsPermissions | PermissionCreate + r.ocsPermissions |= PermissionCreate } if rp.Delete && rp.PurgeRecycle { - r.ocsPermissions = r.ocsPermissions | PermissionDelete + r.ocsPermissions |= PermissionDelete } if rp.AddGrant && rp.RemoveGrant && rp.UpdateGrant { - r.ocsPermissions = r.ocsPermissions | PermissionShare + r.ocsPermissions |= PermissionShare } if r.ocsPermissions.Contain(PermissionRead) { if r.ocsPermissions.Contain(PermissionWrite) && r.ocsPermissions.Contain(PermissionCreate) && r.ocsPermissions.Contain(PermissionDelete) { diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index 3e1672b78c..ee0b68f087 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -254,26 +254,6 @@ func (h *Handler) validatePermissions(w http.ResponseWriter, r *http.Request, st return true } -func (h *Handler) stat(ctx context.Context, path string) (*provider.StatResponse, error) { - c, err := pool.GetGatewayServiceClient(h.gatewayAddr) - if err != nil { - return nil, fmt.Errorf("error getting grpc gateway client: %s", err.Error()) - } - statReq := &provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Path{ - Path: path, - }, - }, - } - - statRes, err := c.Stat(ctx, statReq) - if err != nil { - return nil, fmt.Errorf("error sending a grpc stat request: %s", err.Error()) - } - return statRes, nil -} - // PublicShareContextName represent cross boundaries context for the name of the public share type PublicShareContextName string diff --git a/pkg/storage/fs/ocis/node.go b/pkg/storage/fs/ocis/node.go index 3f2200247f..6f275c69a2 100644 --- a/pkg/storage/fs/ocis/node.go +++ b/pkg/storage/fs/ocis/node.go @@ -53,8 +53,7 @@ type Node struct { ParentID string ID string Name string - ownerID string // used to cache the owner id - ownerIDP string // used to cache the owner idp + owner *userpb.UserId Exists bool } @@ -133,13 +132,17 @@ func ReadRecycleItem(ctx context.Context, lu *Lookup, key string) (n *Node, tras } // lookup ownerId in extended attributes if attrBytes, err = xattr.Get(deletedNodePath, ownerIDAttr); err == nil { - n.ownerID = string(attrBytes) + n.owner = &userpb.UserId{} + n.owner.OpaqueId = string(attrBytes) } else { return } // lookup ownerIdp in extended attributes if attrBytes, err = xattr.Get(deletedNodePath, ownerIDPAttr); err == nil { - n.ownerIDP = string(attrBytes) + if n.owner == nil { + n.owner = &userpb.UserId{} + } + n.owner.Idp = string(attrBytes) } else { return } @@ -274,9 +277,9 @@ func (n *Node) Parent() (p *Node, err error) { // Owner returns the cached owner id or reads it from the extended attributes // TODO can be private as only the AsResourceInfo uses it -func (n *Node) Owner() (id string, idp string, err error) { - if n.ownerID != "" && n.ownerIDP != "" { - return n.ownerID, n.ownerIDP, nil +func (n *Node) Owner() (o *userpb.UserId, err error) { + if n.owner != nil { + return n.owner, nil } // FIXME ... do we return the owner of the reference or the owner of the target? @@ -288,33 +291,37 @@ func (n *Node) Owner() (id string, idp string, err error) { var attrBytes []byte // lookup name in extended attributes if attrBytes, err = xattr.Get(nodePath, ownerIDAttr); err == nil { - n.ownerID = string(attrBytes) + if n.owner == nil { + n.owner = &userpb.UserId{} + } + n.owner.OpaqueId = string(attrBytes) } else { return } // lookup name in extended attributes if attrBytes, err = xattr.Get(nodePath, ownerIDPAttr); err == nil { - n.ownerIDP = string(attrBytes) + if n.owner == nil { + n.owner = &userpb.UserId{} + } + n.owner.Idp = string(attrBytes) } else { return } - return n.ownerID, n.ownerIDP, err + return n.owner, err } // PermissionSet returns the permission set for the current user +// the parent nodes are not takeen into account func (n *Node) PermissionSet(ctx context.Context) *provider.ResourcePermissions { u, ok := user.ContextGetUser(ctx) if !ok { appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("no user in context, returning default permissions") return noPermissions } - n.Owner() // read owner - if u.Id.OpaqueId == n.ownerID && u.Id.Idp == n.ownerIDP { + if o, _ := n.Owner(); isSameUserID(u.Id, o) { return ownerPermissions } - // TODO fix permissions for share recipients by traversing up to the next acl? or to the root? - // ouch ... if we have to read acls for every dir entry that would be n*depth additional xattr reads ... - // but we only need to read the parent tree once. + // read the permissions for the current user from the acls of the current node if np, err := n.ReadUserPermissions(ctx, u); err == nil { return np } @@ -323,7 +330,7 @@ func (n *Node) PermissionSet(ctx context.Context) *provider.ResourcePermissions // AsResourceInfo return the node as CS3 ResourceInfo func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissions, mdKeys []string) (ri *provider.ResourceInfo, err error) { - log := appctx.GetLogger(ctx) + sublog := appctx.GetLogger(ctx).With().Interface("node", n).Logger() var fn string nodePath := n.lu.toInternalPath(n.ID) @@ -368,11 +375,8 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi PermissionSet: rp, } - if owner, idp, err := n.Owner(); err == nil { - ri.Owner = &userpb.UserId{ - Idp: idp, - OpaqueId: owner, - } + if ri.Owner, err = n.Owner(); err != nil { + sublog.Debug().Err(err).Msg("could not determine owner") } // etag currently is a hash of fileid + tmtime (or mtime) @@ -421,12 +425,12 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi if v, err := xattr.Get(nodePath, attrs[i]); err == nil { ri.ArbitraryMetadata.Metadata[k] = string(v) } else { - log.Error().Err(err).Interface("node", n).Str("attr", attrs[i]).Msg("could not get attribute value") + sublog.Error().Err(err).Str("attr", attrs[i]).Msg("could not get attribute value") } } } } else { - log.Error().Err(err).Interface("node", n).Msg("could not list attributes") + sublog.Error().Err(err).Msg("could not list attributes") } if common.FindString(mdKeys, _shareTypesKey) != -1 { @@ -435,7 +439,7 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi } } - log.Debug(). + sublog.Debug(). Interface("ri", ri). Msg("AsResourceInfo") @@ -479,17 +483,18 @@ func (n *Node) UnsetTempEtag() (err error) { // ReadUserPermissions will assemble the permissions for the current user on the given node without parent nodes func (n *Node) ReadUserPermissions(ctx context.Context, u *userpb.User) (ap *provider.ResourcePermissions, err error) { // check if the current user is the owner - id, idp, err := n.Owner() + o, err := n.Owner() if err != nil { // TODO check if a parent folder has the owner set? appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not determine owner, returning default permissions") return noPermissions, err } - if id == "" { + if o.OpaqueId == "" { + // this happens for root nodes in the storage. the extended attributes are set to emptystring to indicate: no owner // TODO what if no owner is set but grants are present? return noOwnerPermissions, nil } - if id == u.Id.OpaqueId && idp == u.Id.Idp { + if isSameUserID(u.Id, o) { appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("user is owner, returning owner permissions") return ownerPermissions, nil } diff --git a/pkg/storage/fs/ocis/ocis.go b/pkg/storage/fs/ocis/ocis.go index eda014e94c..98deac1e8a 100644 --- a/pkg/storage/fs/ocis/ocis.go +++ b/pkg/storage/fs/ocis/ocis.go @@ -26,6 +26,7 @@ import ( "path/filepath" "strings" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" @@ -496,3 +497,14 @@ func (fs *ocisfs) copyMD(s string, t string) (err error) { } return nil } + +func isSameUserID(i *userpb.UserId, j *userpb.UserId) bool { + switch { + case i == nil, j == nil: + return false + case i.OpaqueId == j.OpaqueId && i.Idp == j.Idp: + return true + default: + return false + } +} diff --git a/pkg/storage/fs/ocis/permissions.go b/pkg/storage/fs/ocis/permissions.go index f96cae9068..2b77fb93fa 100644 --- a/pkg/storage/fs/ocis/permissions.go +++ b/pkg/storage/fs/ocis/permissions.go @@ -79,17 +79,18 @@ func (p *Permissions) AssemblePermissions(ctx context.Context, n *Node) (ap *pro return noPermissions, nil } // check if the current user is the owner - id, idp, err := n.Owner() + o, err := n.Owner() if err != nil { // TODO check if a parent folder has the owner set? appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not determine owner, returning default permissions") return noPermissions, err } - if id == "" { + if o.OpaqueId == "" { + // this happens for root nodes in the storage. the extended attributes are set to emptystring to indicate: no owner // TODO what if no owner is set but grants are present? return noOwnerPermissions, nil } - if id == u.Id.OpaqueId && idp == u.Id.Idp { + if isSameUserID(u.Id, o) { appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("user is owner, returning owner permissions") return ownerPermissions, nil } @@ -237,16 +238,17 @@ func (p *Permissions) getUserAndPermissions(ctx context.Context, n *Node) (*user return nil, noPermissions } // check if the current user is the owner - id, _, err := n.Owner() + o, err := n.Owner() if err != nil { appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not determine owner, returning default permissions") return nil, noPermissions } - if id == "" { + if o.OpaqueId == "" { + // this happens for root nodes in the storage. the extended attributes are set to emptystring to indicate: no owner // TODO what if no owner is set but grants are present? return nil, noOwnerPermissions } - if id == u.Id.OpaqueId { + if isSameUserID(u.Id, o) { appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("user is owner, returning owner permissions") return u, ownerPermissions } diff --git a/pkg/storage/fs/ocis/tree.go b/pkg/storage/fs/ocis/tree.go index 0b3b0c88b2..95df12c7a2 100644 --- a/pkg/storage/fs/ocis/tree.go +++ b/pkg/storage/fs/ocis/tree.go @@ -241,15 +241,15 @@ func (t *Tree) Delete(ctx context.Context, n *Node) (err error) { // Prepare the trash // TODO use layout?, but it requires resolving the owners user if the username is used instead of the id. // the node knows the owner id so we use that for now - ownerid, _, err := n.Owner() + o, err := n.Owner() if err != nil { return } - if ownerid == "" { + if o.OpaqueId == "" { // fall back to root trash - ownerid = "root" + o.OpaqueId = "root" } - err = os.MkdirAll(filepath.Join(t.lu.Options.Root, "trash", ownerid), 0700) + err = os.MkdirAll(filepath.Join(t.lu.Options.Root, "trash", o.OpaqueId), 0700) if err != nil { return } @@ -270,7 +270,7 @@ func (t *Tree) Delete(ctx context.Context, n *Node) (err error) { // first make node appear in the owners (or root) trash // parent id and name are stored as extended attributes in the node itself - trashLink := filepath.Join(t.lu.Options.Root, "trash", ownerid, n.ID) + trashLink := filepath.Join(t.lu.Options.Root, "trash", o.OpaqueId, n.ID) err = os.Symlink("../nodes/"+n.ID+".T."+deletionTime, trashLink) if err != nil { // To roll back changes diff --git a/tests/acceptance/features/apiOcisSpecific/apiShareOperations-getWebDAVSharePermissions.feature b/tests/acceptance/features/apiOcisSpecific/apiShareOperations-getWebDAVSharePermissions.feature deleted file mode 100644 index f7881377fa..0000000000 --- a/tests/acceptance/features/apiOcisSpecific/apiShareOperations-getWebDAVSharePermissions.feature +++ /dev/null @@ -1,23 +0,0 @@ -@api @files_sharing-app-required @issue-ocis-reva-47 -Feature: sharing - - Background: - Given using OCS API version "1" - And these users have been created with default attributes and without skeleton files: - | username | - | Alice | - | Brian | - - @issue-ocis-reva-47 - # after fixing all issues delete this Scenario and use the one from oC10 core - Scenario Outline: Empty webdav share-permissions for owned file - Given using DAV path - And user "Alice" has uploaded file with content "foo" to "/tmp.txt" - When user "Alice" gets the following properties of file "/tmp.txt" using the WebDAV API - | propertyName | - | ocs:share-permissions | - Then the single response should contain a property "ocs:share-permissions" with value "5" - Examples: - | dav-path | - | old | - | new | From 7add0098cf7a8658056a39803c92fbadbbcdf5dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 16 Dec 2020 10:10:06 +0000 Subject: [PATCH 10/41] less expected failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../expected-failures-on-OCIS-storage.txt | 54 ++----------------- .../expected-failures-on-OWNCLOUD-storage.txt | 4 -- 2 files changed, 4 insertions(+), 54 deletions(-) diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.txt b/tests/acceptance/expected-failures-on-OCIS-storage.txt index 3d5cd23ebc..4bf071508a 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.txt +++ b/tests/acceptance/expected-failures-on-OCIS-storage.txt @@ -373,8 +373,6 @@ apiShareOperationsToShares/gettingSharesSharedFilteredEmpty.feature:80 # apiShareOperationsToShares/getWebDAVSharePermissions.feature:23 apiShareOperationsToShares/getWebDAVSharePermissions.feature:24 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:142 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:143 # # https://github.com/owncloud/product/issues/203 file_target in share response # @@ -477,6 +475,10 @@ apiSharePublicLink1/createPublicLinkShare.feature:537 apiSharePublicLink1/createPublicLinkShare.feature:538 apiSharePublicLink1/createPublicLinkShare.feature:574 apiSharePublicLink1/createPublicLinkShare.feature:575 +# +apiSharePublicLink1/createPublicLinkShare.feature:592 +apiSharePublicLink1/createPublicLinkShare.feature:593 +# apiSharePublicLink1/createPublicLinkShare.feature:635 apiSharePublicLink1/createPublicLinkShare.feature:636 apiSharePublicLink1/createPublicLinkShare.feature:649 @@ -520,14 +522,8 @@ apiSharePublicLink2/updatePublicLinkShare.feature:285 # # https://github.com/owncloud/product/issues/270 [OCIS] share permissions not enforced # -apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:25 -apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:26 -apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:62 -apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:63 apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:77 apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:78 -apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:136 -apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:137 apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:157 apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:158 apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:179 @@ -635,12 +631,6 @@ apiShareReshareToShares1/reShare.feature:122 apiShareReshareToShares1/reShare.feature:123 apiShareReshareToShares1/reShare.feature:124 apiShareReshareToShares1/reShare.feature:125 -apiShareReshareToShares1/reShare.feature:127 -apiShareReshareToShares1/reShare.feature:128 -apiShareReshareToShares1/reShare.feature:129 -apiShareReshareToShares1/reShare.feature:130 -apiShareReshareToShares1/reShare.feature:131 -apiShareReshareToShares1/reShare.feature:132 apiShareReshareToShares1/reShare.feature:159 apiShareReshareToShares1/reShare.feature:160 apiShareReshareToShares1/reShare.feature:161 @@ -669,14 +659,6 @@ apiShareReshareToShares1/reShare.feature:197 apiShareReshareToShares1/reShare.feature:198 apiShareReshareToShares1/reShare.feature:199 apiShareReshareToShares1/reShare.feature:200 -apiShareReshareToShares1/reShare.feature:202 -apiShareReshareToShares1/reShare.feature:203 -apiShareReshareToShares1/reShare.feature:204 -apiShareReshareToShares1/reShare.feature:205 -apiShareReshareToShares1/reShare.feature:206 -apiShareReshareToShares1/reShare.feature:207 -apiShareReshareToShares1/reShare.feature:208 -apiShareReshareToShares1/reShare.feature:209 apiShareReshareToShares1/reShare.feature:210 apiShareReshareToShares1/reShare.feature:211 apiShareReshareToShares1/reShare.feature:212 @@ -687,8 +669,6 @@ apiShareReshareToShares1/reShare.feature:230 apiShareReshareToShares1/reShare.feature:231 apiShareReshareToShares1/reShare.feature:232 apiShareReshareToShares1/reShare.feature:233 -apiShareReshareToShares1/reShare.feature:235 -apiShareReshareToShares1/reShare.feature:236 apiShareReshareToShares1/reShare.feature:237 apiShareReshareToShares1/reShare.feature:238 apiShareReshareToShares1/reShare.feature:239 @@ -753,14 +733,6 @@ apiShareReshareToShares2/reShareSubfolder.feature:55 apiShareReshareToShares2/reShareSubfolder.feature:56 apiShareReshareToShares2/reShareSubfolder.feature:57 apiShareReshareToShares2/reShareSubfolder.feature:58 -apiShareReshareToShares2/reShareSubfolder.feature:60 -apiShareReshareToShares2/reShareSubfolder.feature:61 -apiShareReshareToShares2/reShareSubfolder.feature:62 -apiShareReshareToShares2/reShareSubfolder.feature:63 -apiShareReshareToShares2/reShareSubfolder.feature:64 -apiShareReshareToShares2/reShareSubfolder.feature:65 -apiShareReshareToShares2/reShareSubfolder.feature:66 -apiShareReshareToShares2/reShareSubfolder.feature:67 apiShareReshareToShares2/reShareSubfolder.feature:68 apiShareReshareToShares2/reShareSubfolder.feature:69 apiShareReshareToShares2/reShareSubfolder.feature:70 @@ -771,8 +743,6 @@ apiShareReshareToShares2/reShareSubfolder.feature:75 apiShareReshareToShares2/reShareSubfolder.feature:76 apiShareReshareToShares2/reShareSubfolder.feature:77 apiShareReshareToShares2/reShareSubfolder.feature:78 -apiShareReshareToShares2/reShareSubfolder.feature:80 -apiShareReshareToShares2/reShareSubfolder.feature:81 apiShareReshareToShares2/reShareSubfolder.feature:82 apiShareReshareToShares2/reShareSubfolder.feature:83 apiShareReshareToShares2/reShareSubfolder.feature:84 @@ -1418,8 +1388,6 @@ apiWebdavEtagPropagation1/moveFileFolder.feature:244 apiWebdavEtagPropagation1/moveFileFolder.feature:245 apiWebdavEtagPropagation1/moveFileFolder.feature:314 apiWebdavEtagPropagation1/moveFileFolder.feature:315 -apiWebdavEtagPropagation2/copyFileFolder.feature:158 -apiWebdavEtagPropagation2/copyFileFolder.feature:159 # # https://github.com/owncloud/product/issues/209 Implement Trashbin Feature for ocis storage # @@ -1847,36 +1815,22 @@ apiShareManagementToShares/moveReceivedShare.feature:70 apiShareManagementToShares/moveReceivedShare.feature:71 apiShareManagementToShares/moveReceivedShare.feature:73 apiShareManagementToShares/moveReceivedShare.feature:88 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:38 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:39 apiShareOperationsToShares/getWebDAVSharePermissions.feature:59 apiShareOperationsToShares/getWebDAVSharePermissions.feature:60 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:73 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:74 apiShareOperationsToShares/getWebDAVSharePermissions.feature:94 apiShareOperationsToShares/getWebDAVSharePermissions.feature:95 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:108 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:109 apiShareOperationsToShares/getWebDAVSharePermissions.feature:129 apiShareOperationsToShares/getWebDAVSharePermissions.feature:130 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:157 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:158 apiShareOperationsToShares/getWebDAVSharePermissions.feature:177 apiShareOperationsToShares/getWebDAVSharePermissions.feature:178 apiShareOperationsToShares/getWebDAVSharePermissions.feature:191 apiShareOperationsToShares/getWebDAVSharePermissions.feature:192 apiShareOperationsToShares/getWebDAVSharePermissions.feature:212 apiShareOperationsToShares/getWebDAVSharePermissions.feature:213 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:226 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:227 apiShareOperationsToShares/getWebDAVSharePermissions.feature:247 apiShareOperationsToShares/getWebDAVSharePermissions.feature:248 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:261 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:262 apiShareOperationsToShares/getWebDAVSharePermissions.feature:282 apiShareOperationsToShares/getWebDAVSharePermissions.feature:283 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:296 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:297 apiShareOperationsToShares/getWebDAVSharePermissions.feature:317 apiShareOperationsToShares/getWebDAVSharePermissions.feature:318 apiShareUpdateToShares/updateShare.feature:92 diff --git a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt index c970d2e5e6..fe70106fc5 100644 --- a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt +++ b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt @@ -359,8 +359,6 @@ apiShareOperationsToShares/gettingSharesSharedFilteredEmpty.feature:80 # apiShareOperationsToShares/getWebDAVSharePermissions.feature:23 apiShareOperationsToShares/getWebDAVSharePermissions.feature:24 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:142 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:143 # # https://github.com/owncloud/ocis-reva/issues/282 Split old public API webdav tests from new public webdav tests # https://github.com/owncloud/ocis-reva/issues/292 Public link enforce permissions @@ -1798,8 +1796,6 @@ apiShareOperationsToShares/getWebDAVSharePermissions.feature:108 apiShareOperationsToShares/getWebDAVSharePermissions.feature:109 apiShareOperationsToShares/getWebDAVSharePermissions.feature:129 apiShareOperationsToShares/getWebDAVSharePermissions.feature:130 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:157 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:158 apiShareOperationsToShares/getWebDAVSharePermissions.feature:177 apiShareOperationsToShares/getWebDAVSharePermissions.feature:178 apiShareOperationsToShares/getWebDAVSharePermissions.feature:191 From 2a5d5b0971dcab883b8ab8b4b4e3f55ea3494048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 16 Dec 2020 10:14:53 +0000 Subject: [PATCH 11/41] add changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../share-permissions-and-resource-permissions.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 changelog/unreleased/share-permissions-and-resource-permissions.md diff --git a/changelog/unreleased/share-permissions-and-resource-permissions.md b/changelog/unreleased/share-permissions-and-resource-permissions.md new file mode 100644 index 0000000000..e3b3abcf26 --- /dev/null +++ b/changelog/unreleased/share-permissions-and-resource-permissions.md @@ -0,0 +1,9 @@ +Enhancement: calculate and expose actual file permission set + +Instead of hardcoding the permissions set for every file and folder to ListContainer:true, CreateContainer:true and always reporting the hardcoded string WCKDNVR for the WebDAV permissions we now aggregate the actual cs3 resource permission set in the storage drivers and correctly map them to ocs permissions and webdav permissions using a common role struct that encapsulates the mapping logic. + +https://github.com/cs3org/reva/pull/1368 +https://github.com/owncloud/ocis/issues/552 +https://github.com/owncloud/ocis/issues/893 +https://github.com/owncloud/product/issues/270 +https://github.com/owncloud/ocis-reva/issues/47 \ No newline at end of file From 15c657e53a68a45353fe36626a996ecefcf0a548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 16 Dec 2020 10:59:57 +0000 Subject: [PATCH 12/41] less expected failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt index fe70106fc5..af4b6b0a81 100644 --- a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt +++ b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt @@ -447,6 +447,10 @@ apiSharePublicLink1/createPublicLinkShare.feature:537 apiSharePublicLink1/createPublicLinkShare.feature:538 apiSharePublicLink1/createPublicLinkShare.feature:574 apiSharePublicLink1/createPublicLinkShare.feature:575 +# +apiSharePublicLink1/createPublicLinkShare.feature:592 +apiSharePublicLink1/createPublicLinkShare.feature:593 +# apiSharePublicLink1/createPublicLinkShare.feature:635 apiSharePublicLink1/createPublicLinkShare.feature:636 apiSharePublicLink1/createPublicLinkShare.feature:649 From cd4823418d5247351a77efe54b8840b91466352c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 16 Dec 2020 14:11:10 +0000 Subject: [PATCH 13/41] cleanup after rebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../services/owncloud/ocs/handlers/apps/sharing/shares/user.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go index 9be7c34b27..e802562c40 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go @@ -155,6 +155,7 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn return } h.addDisplaynames(ctx, c, s) + h.mapUserIds(ctx, c, s) response.WriteOCSSuccess(w, r, s) } From 11e06d0a4ae5545d4d9814379a4362c0e8cc764b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 17 Dec 2020 11:08:16 +0000 Subject: [PATCH 14/41] enforce public share permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../publicstorageprovider.go | 97 +++++++++++++++---- 1 file changed, 78 insertions(+), 19 deletions(-) diff --git a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go index 7fbd345011..84c11f3348 100644 --- a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go +++ b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go @@ -131,16 +131,16 @@ func (s *service) InitiateFileDownload(ctx context.Context, req *provider.Initia return s.initiateFileDownload(ctx, req) } -func (s *service) translatePublicRefToCS3Ref(ctx context.Context, ref *provider.Reference) (*provider.Reference, string, error) { +func (s *service) translatePublicRefToCS3Ref(ctx context.Context, ref *provider.Reference) (*provider.Reference, string, *link.PublicShare, error) { log := appctx.GetLogger(ctx) tkn, relativePath, err := s.unwrap(ctx, ref) if err != nil { - return nil, "", err + return nil, "", nil, err } - originalPath, err := s.pathFromToken(ctx, tkn) + originalPath, sh, err := s.resolveToken(ctx, tkn) if err != nil { - return nil, "", err + return nil, "", nil, err } cs3Ref := &provider.Reference{ @@ -149,11 +149,12 @@ func (s *service) translatePublicRefToCS3Ref(ctx context.Context, ref *provider. log.Debug(). Interface("sourceRef", ref). Interface("cs3Ref", cs3Ref). + Interface("share", sh). Str("tkn", tkn). Str("originalPath", originalPath). Str("relativePath", relativePath). Msg("translatePublicRefToCS3Ref") - return cs3Ref, tkn, nil + return cs3Ref, tkn, sh, nil } // Both, t.dir and tokenPath paths need to be merged: @@ -164,10 +165,15 @@ func (s *service) translatePublicRefToCS3Ref(ctx context.Context, ref *provider. // end = /einstein/files/public-links/foldera/folderb/ func (s *service) initiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*provider.InitiateFileDownloadResponse, error) { - cs3Ref, _, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) + cs3Ref, _, sh, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) if err != nil { return nil, err } + if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.InitiateFileDownload { + return &provider.InitiateFileDownloadResponse{ + Status: status.NewPermissionDenied(ctx, nil, "share does not grant InitiateFileDownload permission"), + }, nil + } dReq := &provider.InitiateFileDownloadRequest{ Ref: cs3Ref, } @@ -207,10 +213,15 @@ func (s *service) initiateFileDownload(ctx context.Context, req *provider.Initia } func (s *service) InitiateFileUpload(ctx context.Context, req *provider.InitiateFileUploadRequest) (*provider.InitiateFileUploadResponse, error) { - cs3Ref, _, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) + cs3Ref, _, sh, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) if err != nil { return nil, err } + if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.InitiateFileUpload { + return &provider.InitiateFileUploadResponse{ + Status: status.NewPermissionDenied(ctx, nil, "share does not grant InitiateFileUpload permission"), + }, nil + } uReq := &provider.InitiateFileUploadRequest{ Ref: cs3Ref, Opaque: req.Opaque, @@ -273,10 +284,15 @@ func (s *service) CreateContainer(ctx context.Context, req *provider.CreateConta trace.StringAttribute("ref", req.Ref.String()), ) - cs3Ref, _, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) + cs3Ref, _, sh, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) if err != nil { return nil, err } + if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.CreateContainer { + return &provider.CreateContainerResponse{ + Status: status.NewPermissionDenied(ctx, nil, "share does not grant CreateContainer permission"), + }, nil + } var res *provider.CreateContainerResponse // the call has to be made to the gateway instead of the storage. @@ -303,10 +319,15 @@ func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*pro trace.StringAttribute("ref", req.Ref.String()), ) - cs3Ref, _, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) + cs3Ref, _, sh, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) if err != nil { return nil, err } + if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.Delete { + return &provider.DeleteResponse{ + Status: status.NewPermissionDenied(ctx, nil, "share does not grant Delete permission"), + }, nil + } var res *provider.DeleteResponse // the call has to be made to the gateway instead of the storage. @@ -334,12 +355,17 @@ func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provide trace.StringAttribute("destination", req.Destination.String()), ) - cs3RefSource, tknSource, err := s.translatePublicRefToCS3Ref(ctx, req.Source) + cs3RefSource, tknSource, sh, err := s.translatePublicRefToCS3Ref(ctx, req.Source) if err != nil { return nil, err } + if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.Move { + return &provider.MoveResponse{ + Status: status.NewPermissionDenied(ctx, nil, "share does not grant Move permission"), + }, nil + } // FIXME: maybe there's a shortcut possible here using the source path - cs3RefDestination, tknDest, err := s.translatePublicRefToCS3Ref(ctx, req.Destination) + cs3RefDestination, tknDest, _, err := s.translatePublicRefToCS3Ref(ctx, req.Destination) if err != nil { return nil, err } @@ -381,10 +407,15 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide return nil, err } - originalPath, err := s.pathFromToken(ctx, tkn) + originalPath, sh, err := s.resolveToken(ctx, tkn) if err != nil { return nil, err } + if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.Stat { + return &provider.StatResponse{ + Status: status.NewPermissionDenied(ctx, nil, "share does not grant Stat permission"), + }, nil + } var statResponse *provider.StatResponse // the call has to be made to the gateway instead of the storage. @@ -404,6 +435,7 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide // prevent leaking internal paths if statResponse.Info != nil { statResponse.Info.Path = path.Join(s.mountPath, "/", tkn, relativePath) + filterPermissions(statResponse.Info.PermissionSet, sh.GetPermissions().Permissions) } return statResponse, nil @@ -419,10 +451,15 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer return nil, err } - pathFromToken, err := s.pathFromToken(ctx, tkn) + pathFromToken, sh, err := s.resolveToken(ctx, tkn) if err != nil { return nil, err } + if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.ListContainer { + return &provider.ListContainerResponse{ + Status: status.NewPermissionDenied(ctx, nil, "share does not grant ListContainer permission"), + }, nil + } listContainerR, err := s.gateway.ListContainer( ctx, @@ -441,12 +478,34 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer } for i := range listContainerR.Infos { + filterPermissions(listContainerR.Infos[i].PermissionSet, sh.GetPermissions().Permissions) listContainerR.Infos[i].Path = path.Join(s.mountPath, "/", tkn, relativePath, path.Base(listContainerR.Infos[i].Path)) } return listContainerR, nil } +func filterPermissions(l *provider.ResourcePermissions, r *provider.ResourcePermissions) { + l.AddGrant = l.AddGrant && r.AddGrant + l.CreateContainer = l.CreateContainer && r.CreateContainer + l.Delete = l.Delete && r.Delete + l.GetPath = l.GetPath && r.GetPath + l.GetQuota = l.GetQuota && r.GetQuota + l.InitiateFileDownload = l.InitiateFileDownload && r.InitiateFileDownload + l.InitiateFileUpload = l.InitiateFileUpload && r.InitiateFileUpload + l.ListContainer = l.ListContainer && r.ListContainer + l.ListFileVersions = l.ListFileVersions && r.ListFileVersions + l.ListGrants = l.ListGrants && r.ListGrants + l.ListRecycle = l.ListRecycle && r.ListRecycle + l.Move = l.Move && r.Move + l.PurgeRecycle = l.PurgeRecycle && r.PurgeRecycle + l.RemoveGrant = l.RemoveGrant && r.RemoveGrant + l.RestoreFileVersion = l.RestoreFileVersion && r.RestoreFileVersion + l.RestoreRecycleItem = l.RestoreRecycleItem && r.RestoreRecycleItem + l.Stat = l.Stat && r.Stat + l.UpdateGrant = l.UpdateGrant && r.UpdateGrant +} + func (s *service) unwrap(ctx context.Context, ref *provider.Reference) (token string, relativePath string, err error) { if ref.GetId() != nil { return "", "", errors.New("need path based ref: got " + ref.String()) @@ -533,11 +592,11 @@ func (s *service) trimMountPrefix(fn string) (string, error) { return "", errors.New(fmt.Sprintf("path=%q does not belong to this storage provider mount path=%q"+fn, s.mountPath)) } -// pathFromToken returns the path for the publicly shared resource. -func (s *service) pathFromToken(ctx context.Context, token string) (string, error) { +// resolveToken returns the path and share for the publicly shared resource. +func (s *service) resolveToken(ctx context.Context, token string) (string, *link.PublicShare, error) { driver, err := pool.GetGatewayServiceClient(s.conf.GatewayAddr) if err != nil { - return "", err + return "", nil, err } publicShareResponse, err := driver.GetPublicShare( @@ -551,15 +610,15 @@ func (s *service) pathFromToken(ctx context.Context, token string) (string, erro }, ) if err != nil { - return "", err + return "", nil, err } pathRes, err := s.gateway.GetPath(ctx, &provider.GetPathRequest{ ResourceId: publicShareResponse.GetShare().GetResourceId(), }) if err != nil { - return "", err + return "", nil, err } - return pathRes.Path, nil + return pathRes.Path, publicShareResponse.GetShare(), nil } From ef6daa5f0d1feeb3c6901eacc91ba661f4817008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 18 Dec 2020 09:18:41 +0000 Subject: [PATCH 15/41] implement files drop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../publicstorageprovider.go | 24 +++++- .../http/services/owncloud/ocdav/propfind.go | 82 +++++++++++++++++-- .../services/owncloud/ocs/conversions/main.go | 29 +------ .../services/owncloud/ocs/conversions/role.go | 28 +++++-- .../expected-failures-on-OCIS-storage.txt | 12 --- ...ublicLink2-uploadToPublicLinkShare.feature | 2 +- 6 files changed, 123 insertions(+), 54 deletions(-) diff --git a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go index 84c11f3348..3be08019b4 100644 --- a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go +++ b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go @@ -20,6 +20,7 @@ package publicstorageprovider import ( "context" + "encoding/json" "fmt" "path" "strings" @@ -28,6 +29,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rgrpc" "github.com/cs3org/reva/pkg/rgrpc/status" @@ -407,11 +409,11 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide return nil, err } - originalPath, sh, err := s.resolveToken(ctx, tkn) + originalPath, ls, err := s.resolveToken(ctx, tkn) if err != nil { return nil, err } - if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.Stat { + if ls.GetPermissions() == nil || !ls.GetPermissions().Permissions.Stat { return &provider.StatResponse{ Status: status.NewPermissionDenied(ctx, nil, "share does not grant Stat permission"), }, nil @@ -434,13 +436,29 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide // prevent leaking internal paths if statResponse.Info != nil { + addShare(statResponse.Info, ls) statResponse.Info.Path = path.Join(s.mountPath, "/", tkn, relativePath) - filterPermissions(statResponse.Info.PermissionSet, sh.GetPermissions().Permissions) + filterPermissions(statResponse.Info.PermissionSet, ls.GetPermissions().Permissions) } return statResponse, nil } +func addShare(i *provider.ResourceInfo, ls *link.PublicShare) error { + if i.Opaque == nil { + i.Opaque = &typesv1beta1.Opaque{} + } + if i.Opaque.Map == nil { + i.Opaque.Map = map[string]*typesv1beta1.OpaqueEntry{} + } + val, err := json.Marshal(ls) + if err != nil { + return err + } + i.Opaque.Map["link-share"] = &typesv1beta1.OpaqueEntry{Decoder: "json", Value: val} + return nil +} + func (s *service) ListContainerStream(req *provider.ListContainerStreamRequest, ss provider.ProviderAPI_ListContainerStreamServer) error { return gstatus.Errorf(codes.Unimplemented, "method not implemented") } diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 423e172817..d24ce4655e 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -21,6 +21,7 @@ package ocdav import ( "bytes" "context" + "encoding/json" "encoding/xml" "fmt" "io" @@ -35,6 +36,7 @@ import ( userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/pkg/appctx" @@ -278,6 +280,17 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide Propstat: []propstatXML{}, } + var ls *link.PublicShare + if md.Opaque != nil && md.Opaque.Map != nil && md.Opaque.Map["link-share"] != nil && md.Opaque.Map["link-share"].Decoder == "json" { + ls = &link.PublicShare{} + err := json.Unmarshal(md.Opaque.Map["link-share"].Value, ls) + if err != nil { + sublog.Error().Err(err).Msg("could not unmarshal link json") + } + } + + isShared := !isCurrentUserOwner(ctx, md.Owner) + // when allprops has been requested if pf.Allprop != nil { // return all known properties @@ -305,7 +318,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide r := conversions.RoleFromResourcePermissions(md.PermissionSet) wdp := r.WebDAVPermissions( md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, - !isCurrentUserOwner(ctx, md.Owner), + isShared, false, false, ) @@ -393,7 +406,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide r := conversions.RoleFromResourcePermissions(md.PermissionSet) wdp := r.WebDAVPermissions( md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, - !isCurrentUserOwner(ctx, md.Owner), + isShared, false, false, ) @@ -404,6 +417,56 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } else { propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:permissions", "")) } + case "public-link-permission": // only on a share root node + if ls != nil && md.PermissionSet != nil { + r := conversions.RoleFromResourcePermissions(md.PermissionSet) + propstatOK.Prop = append( + propstatOK.Prop, + s.newProp("oc:public-link-permission", strconv.FormatUint(uint64(r.OCSPermissions()), 10))) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-permission", "")) + } + case "public-link-item-type": // only on a share root node + if ls != nil { + if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-item-type", "folder")) + } else { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-item-type", "file")) + // redirectref is another option + } + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-item-type", "")) + } + case "public-link-share-datetime": + if ls != nil && ls.Mtime != nil { + t := utils.TSToTime(ls.Mtime).UTC() // TODO or ctime? + shareTimeString := t.Format(time.RFC1123Z) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-share-datetime", shareTimeString)) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-datetime", "")) + } + case "public-link-share-owner": + if ls != nil && ls.Owner != nil { + if isCurrentUserOwner(ctx, ls.Owner) { + u := ctxuser.ContextMustGetUser(ctx) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-share-owner", u.Username)) + } else { + u, _ := ctxuser.ContextGetUser(ctx) + sublog.Error().Interface("share", ls).Interface("user", u).Msg("the current user in the context should be the owner of a public link share") + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-owner", "")) + } + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-owner", "")) + } + case "public-link-expiration": + if ls != nil && ls.Expiration != nil { + t := utils.TSToTime(ls.Expiration).UTC() + expireTimeString := t.Format(time.RFC1123Z) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-expiration", expireTimeString)) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-expiration", "")) + } + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-expiration", "")) case "size": // phoenix only // TODO we cannot find out if md.Size is set or not because ints in go default to 0 // oc:size is also available on folders @@ -449,8 +512,17 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) } case "owner-display-name": // phoenix only - // TODO(jfd): lookup displayname? or let clients do that? They should cache that IMO - fallthrough + if md.Owner != nil { + if isCurrentUserOwner(ctx, md.Owner) { + u := ctxuser.ContextMustGetUser(ctx) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-display-name", u.Username)) + } else { + sublog.Debug().Msg("TODO fetch user displayname") + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-display-name", "")) + } + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-display-name", "")) + } case "privatelink": // phoenix only // https://phoenix.owncloud.com/f/9 fallthrough @@ -512,7 +584,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide switch pf.Prop[i].Local { case "share-permissions": if md.PermissionSet != nil { - perms := conversions.Permissions2OCSPermissions(md.PermissionSet) + perms := conversions.RoleFromResourcePermissions(md.PermissionSet).OCSPermissions() propstatOK.Prop = append(propstatOK.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, strconv.FormatUint(uint64(perms), 10))) } default: diff --git a/internal/http/services/owncloud/ocs/conversions/main.go b/internal/http/services/owncloud/ocs/conversions/main.go index 07d0bff147..eee896d080 100644 --- a/internal/http/services/owncloud/ocs/conversions/main.go +++ b/internal/http/services/owncloud/ocs/conversions/main.go @@ -32,7 +32,6 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" publicsharemgr "github.com/cs3org/reva/pkg/publicshare/manager/registry" usermgr "github.com/cs3org/reva/pkg/user/manager/registry" @@ -251,7 +250,7 @@ func UserIDToString(userID *userpb.UserId) string { // UserSharePermissions2OCSPermissions transforms cs3api permissions into OCS Permissions data model func UserSharePermissions2OCSPermissions(sp *collaboration.SharePermissions) Permissions { if sp != nil { - return Permissions2OCSPermissions(sp.GetPermissions()) + return RoleFromResourcePermissions(sp.GetPermissions()).OCSPermissions() } return PermissionInvalid } @@ -276,35 +275,11 @@ func GetPublicShareManager(manager string, m map[string]map[string]interface{}) func publicSharePermissions2OCSPermissions(sp *link.PublicSharePermissions) Permissions { if sp != nil { - return Permissions2OCSPermissions(sp.GetPermissions()) + return RoleFromResourcePermissions(sp.GetPermissions()).OCSPermissions() } return PermissionInvalid } -// TODO sort out mapping, this is just a first guess -// public link permissions to OCS permissions -func Permissions2OCSPermissions(p *provider.ResourcePermissions) Permissions { - permissions := PermissionInvalid - if p != nil { - if p.ListContainer { - permissions += PermissionRead - } - if p.InitiateFileUpload { - permissions += PermissionWrite - } - if p.CreateContainer { - permissions += PermissionCreate - } - if p.Delete { - permissions += PermissionDelete - } - if p.AddGrant { - permissions += PermissionShare - } - } - return permissions -} - // timestamp is assumed to be UTC ... just human readable ... // FIXME and ambiguous / error prone because there is no time zone ... func timestampToExpiration(t *types.Timestamp) string { diff --git a/internal/http/services/owncloud/ocs/conversions/role.go b/internal/http/services/owncloud/ocs/conversions/role.go index 347b915f2f..df6313ec6d 100644 --- a/internal/http/services/owncloud/ocs/conversions/role.go +++ b/internal/http/services/owncloud/ocs/conversions/role.go @@ -259,19 +259,22 @@ func NewCoownerRole() *Role { } // NewUploaderRole creates an uploader role -// TODO check this works properly func NewUploaderRole() *Role { return &Role{ Name: RoleViewer, cS3ResourcePermissions: &provider.ResourcePermissions{ + // he will need to make stat requests + // TODO and List requests + Stat: true, + ListContainer: true, // read GetPath: true, - // he will need to make stat requests - Stat: true, + // mkdir + CreateContainer: true, // upload InitiateFileUpload: true, }, - ocsPermissions: PermissionWrite, + ocsPermissions: PermissionCreate, } } @@ -288,6 +291,9 @@ func RoleFromOCSPermissions(p Permissions) *Role { return NewViewerRole() } } + if p == PermissionCreate { + return NewUploaderRole() + } // legacy return NewLegacyRoleFromOCSPermissions(p) } @@ -315,8 +321,12 @@ func NewLegacyRoleFromOCSPermissions(p Permissions) *Role { r.cS3ResourcePermissions.RestoreRecycleItem = true } if p.Contain(PermissionCreate) { + r.cS3ResourcePermissions.Stat = true + r.cS3ResourcePermissions.ListContainer = true r.cS3ResourcePermissions.CreateContainer = true - // FIXME permissions mismatch: double check create vs write file + // FIXME permissions mismatch: double check ocs create vs update file + // - if the file exists the ocs api needs to check update permisson, + // - if the file does not exist the ocs api needs to check update permission r.cS3ResourcePermissions.InitiateFileUpload = true if p.Contain(PermissionWrite) { r.cS3ResourcePermissions.Move = true // TODO move only when create and write? @@ -356,7 +366,9 @@ func RoleFromResourcePermissions(rp *provider.ResourcePermissions) *Role { rp.RestoreRecycleItem { r.ocsPermissions |= PermissionWrite } - if rp.CreateContainer && + if rp.ListContainer && + rp.Stat && + rp.CreateContainer && rp.InitiateFileUpload { r.ocsPermissions |= PermissionCreate } @@ -382,6 +394,10 @@ func RoleFromResourcePermissions(rp *provider.ResourcePermissions) *Role { return r } } + if r.ocsPermissions == PermissionCreate { + r.Name = RoleUploader + return r + } r.Name = RoleLegacy // at this point other ocs permissions may have been mapped. // TODO what about even more granular cs3 permissions?, eg. only stat diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.txt b/tests/acceptance/expected-failures-on-OCIS-storage.txt index 4bf071508a..42881b906d 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.txt +++ b/tests/acceptance/expected-failures-on-OCIS-storage.txt @@ -419,9 +419,7 @@ apiSharePublicLink1/changingPublicLinkShare.feature:244 # https://github.com/owncloud/ocis-reva/issues/292 Public link enforce permissions # apiSharePublicLink1/changingPublicLinkShare.feature:267 -apiSharePublicLink1/changingPublicLinkShare.feature:278 apiSharePublicLink1/changingPublicLinkShare.feature:289 -apiSharePublicLink1/changingPublicLinkShare.feature:300 apiSharePublicLink1/createPublicLinkShare.feature:34 apiSharePublicLink1/createPublicLinkShare.feature:35 apiSharePublicLink1/createPublicLinkShare.feature:183 @@ -453,8 +451,6 @@ apiSharePublicLink1/createPublicLinkShare.feature:156 # # https://github.com/owncloud/ocis-reva/issues/41 various sharing settings cannot be set # -apiSharePublicLink1/createPublicLinkShare.feature:389 -apiSharePublicLink1/createPublicLinkShare.feature:390 apiSharePublicLink1/createPublicLinkShare.feature:410 apiSharePublicLink1/createPublicLinkShare.feature:411 apiSharePublicLink1/createPublicLinkShare.feature:431 @@ -529,11 +525,6 @@ apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:158 apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:179 apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:180 # -# https://github.com/owncloud/ocis-reva/issues/292 Public link enforce permissions -# -apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:97 -apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:98 -# # https://github.com/owncloud/product/issues/272 [OCIS] old public webdav api doesnt works # apiSharePublicLink2/reShareAsPublicLinkToSharesOldDav.feature:30 @@ -568,12 +559,9 @@ apiSharePublicLink2/updatePublicLinkShare.feature:440 # # https://github.com/owncloud/ocis-reva/issues/292 Public link enforce permissions # -apiSharePublicLink2/updatePublicLinkShare.feature:461 -apiSharePublicLink2/updatePublicLinkShare.feature:462 apiSharePublicLink2/updatePublicLinkShare.feature:486 apiSharePublicLink2/updatePublicLinkShare.feature:487 apiSharePublicLink2/uploadToPublicLinkShare.feature:9 -apiSharePublicLink2/uploadToPublicLinkShare.feature:74 apiSharePublicLink2/uploadToPublicLinkShare.feature:83 apiSharePublicLink2/uploadToPublicLinkShare.feature:103 apiSharePublicLink2/uploadToPublicLinkShare.feature:121 diff --git a/tests/acceptance/features/apiOcisSpecific/apiSharePublicLink2-uploadToPublicLinkShare.feature b/tests/acceptance/features/apiOcisSpecific/apiSharePublicLink2-uploadToPublicLinkShare.feature index 320ce525b6..d706bbf7d0 100644 --- a/tests/acceptance/features/apiOcisSpecific/apiSharePublicLink2-uploadToPublicLinkShare.feature +++ b/tests/acceptance/features/apiOcisSpecific/apiSharePublicLink2-uploadToPublicLinkShare.feature @@ -14,4 +14,4 @@ Feature: upload to a public link share | permissions | create | When user "Alice" deletes file "/FOLDER" using the WebDAV API And the public uploads file "does-not-matter.txt" with content "does not matter" using the new public WebDAV API - Then the HTTP status code should be "500" + Then the HTTP status code should be "403" From 8683de2e850fc993eb1bd765fead5c3865cc2aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 18 Dec 2020 11:20:03 +0000 Subject: [PATCH 16/41] file shares never have delete or create permission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../publicstorageprovider/publicstorageprovider.go | 4 +++- internal/http/services/owncloud/ocdav/dav.go | 8 ++++++-- .../owncloud/ocs/handlers/apps/sharing/shares/public.go | 9 +++++++++ .../owncloud/ocs/handlers/apps/sharing/shares/remote.go | 8 ++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go index 3be08019b4..ac69fa54ed 100644 --- a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go +++ b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go @@ -436,7 +436,9 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide // prevent leaking internal paths if statResponse.Info != nil { - addShare(statResponse.Info, ls) + if err := addShare(statResponse.Info, ls); err != nil { + appctx.GetLogger(ctx).Error().Err(err).Interface("share", ls).Interface("info", statResponse.Info).Msg("error when adding share") + } statResponse.Info.Path = path.Join(s.mountPath, "/", tkn, relativePath) filterPermissions(statResponse.Info.PermissionSet, ls.GetPermissions().Permissions) } diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index bae19d7087..6f1d500eb4 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -171,7 +171,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { w.WriteHeader(http.StatusNotFound) } - _, pass, _ := r.BasicAuth() + _, pass, hasBasicAuth := r.BasicAuth() token, _ := router.ShiftPath(r.URL.Path) authenticateRequest := gatewayv1beta1.AuthenticateRequest{ @@ -186,7 +186,11 @@ func (h *DavHandler) Handler(s *svc) http.Handler { return } if res.Status.Code == rpcv1beta1.Code_CODE_UNAUTHENTICATED { - w.WriteHeader(http.StatusUnauthorized) + if hasBasicAuth { + w.WriteHeader(http.StatusUnauthorized) + } else { + w.WriteHeader(http.StatusNotFound) + } return } diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go index ddf30c041a..31bd739e17 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go @@ -70,6 +70,15 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request, } } + if statInfo != nil && statInfo.Type == provider.ResourceType_RESOURCE_TYPE_FILE { + // Single file shares should never have delete or create permissions + role := conversions.RoleFromResourcePermissions(newPermissions) + permissions := role.OCSPermissions() + permissions &^= conversions.PermissionCreate + permissions &^= conversions.PermissionDelete + newPermissions = conversions.RoleFromOCSPermissions(permissions).CS3ResourcePermissions() + } + req := link.CreatePublicShareRequest{ ResourceInfo: statInfo, Grant: &link.Grant{ diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go index ad849582a2..33fe7df96c 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go @@ -90,6 +90,14 @@ func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Reque role = conversions.RoleFromOCSPermissions(permissions) } + if statInfo != nil && statInfo.Type == provider.ResourceType_RESOURCE_TYPE_FILE { + // Single file shares should never have delete or create permissions + permissions := role.OCSPermissions() + permissions &^= conversions.PermissionCreate + permissions &^= conversions.PermissionDelete + role = conversions.RoleFromOCSPermissions(permissions) + } + createShareReq := &ocm.CreateOCMShareRequest{ Opaque: &types.Opaque{ Map: map[string]*types.OpaqueEntry{ From 5e7afad2334f5376a16342dd9504adb42a3394a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 18 Dec 2020 13:05:24 +0000 Subject: [PATCH 17/41] fix typo --- internal/http/services/owncloud/ocs/conversions/role.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/http/services/owncloud/ocs/conversions/role.go b/internal/http/services/owncloud/ocs/conversions/role.go index df6313ec6d..5ed0aa5b96 100644 --- a/internal/http/services/owncloud/ocs/conversions/role.go +++ b/internal/http/services/owncloud/ocs/conversions/role.go @@ -325,7 +325,7 @@ func NewLegacyRoleFromOCSPermissions(p Permissions) *Role { r.cS3ResourcePermissions.ListContainer = true r.cS3ResourcePermissions.CreateContainer = true // FIXME permissions mismatch: double check ocs create vs update file - // - if the file exists the ocs api needs to check update permisson, + // - if the file exists the ocs api needs to check update permission, // - if the file does not exist the ocs api needs to check update permission r.cS3ResourcePermissions.InitiateFileUpload = true if p.Contain(PermissionWrite) { From 390604c66883bd2f0016efeea3475e22f10c1bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 18 Dec 2020 13:05:52 +0000 Subject: [PATCH 18/41] files never have a create permission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../http/services/owncloud/ocdav/propfind.go | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index d24ce4655e..691de73e5c 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -280,6 +280,12 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide Propstat: []propstatXML{}, } + // files never have the create container permission + if md.Type == provider.ResourceType_RESOURCE_TYPE_FILE { + md.PermissionSet = copyPermissionSet(md.PermissionSet) + md.PermissionSet.CreateContainer = false + } + var ls *link.PublicShare if md.Opaque != nil && md.Opaque.Map != nil && md.Opaque.Map["link-share"] != nil && md.Opaque.Map["link-share"].Decoder == "json" { ls = &link.PublicShare{} @@ -287,6 +293,11 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide if err != nil { sublog.Error().Err(err).Msg("could not unmarshal link json") } + // shared files never have the delete permission + if md.Type == provider.ResourceType_RESOURCE_TYPE_FILE { + // the permission set has already been copied above + md.PermissionSet.Delete = false + } } isShared := !isCurrentUserOwner(ctx, md.Owner) @@ -614,6 +625,30 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide return &response, nil } +// the permission set that is passed in needs to be copied so we don't accidentially overwrite the permission set of other entries +func copyPermissionSet(p *provider.ResourcePermissions) *provider.ResourcePermissions { + return &provider.ResourcePermissions{ + AddGrant: p.AddGrant, + CreateContainer: p.CreateContainer, + Delete: p.Delete, + GetPath: p.GetPath, + GetQuota: p.GetQuota, + InitiateFileDownload: p.InitiateFileDownload, + InitiateFileUpload: p.InitiateFileUpload, + ListGrants: p.ListGrants, + ListContainer: p.ListContainer, + ListFileVersions: p.ListFileVersions, + ListRecycle: p.ListRecycle, + Move: p.Move, + RemoveGrant: p.RemoveGrant, + PurgeRecycle: p.PurgeRecycle, + RestoreFileVersion: p.RestoreFileVersion, + RestoreRecycleItem: p.RestoreRecycleItem, + Stat: p.Stat, + UpdateGrant: p.UpdateGrant, + } +} + // a file is only yours if you are the owner func isCurrentUserOwner(ctx context.Context, owner *userv1beta1.UserId) bool { contextUser, ok := ctxuser.ContextGetUser(ctx) From cf51af000fbe59e8f12c992942638cbddc823542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 18 Dec 2020 14:33:40 +0000 Subject: [PATCH 19/41] differentiate not found vs unauthenticated for link tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../services/authprovider/authprovider.go | 29 ++++++++++++------- .../grpc/services/gateway/authprovider.go | 19 +++++++----- .../publicshareprovider.go | 28 +++++++++++++----- internal/http/services/owncloud/ocdav/dav.go | 20 +++++++------ .../http/services/owncloud/ocdav/propfind.go | 3 ++ pkg/auth/manager/publicshares/publicshares.go | 11 ++++++- pkg/publicshare/manager/json/json.go | 5 ++-- pkg/publicshare/manager/memory/memory.go | 3 +- .../expected-failures-on-OCIS-storage.txt | 4 --- .../expected-failures-on-OWNCLOUD-storage.txt | 21 -------------- 10 files changed, 79 insertions(+), 64 deletions(-) diff --git a/internal/grpc/services/authprovider/authprovider.go b/internal/grpc/services/authprovider/authprovider.go index c7cf0b03ee..b574c4fc28 100644 --- a/internal/grpc/services/authprovider/authprovider.go +++ b/internal/grpc/services/authprovider/authprovider.go @@ -26,6 +26,7 @@ import ( "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/auth" "github.com/cs3org/reva/pkg/auth/manager/registry" + "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/mitchellh/mapstructure" @@ -108,18 +109,26 @@ func (s *service) Authenticate(ctx context.Context, req *provider.AuthenticateRe password := req.ClientSecret u, err := s.authmgr.Authenticate(ctx, username, password) - if err != nil { + switch v := err.(type) { + case nil: + log.Info().Msgf("user %s authenticated", u.String()) + return &provider.AuthenticateResponse{ + Status: status.NewOK(ctx), + User: u, + }, nil + case errtypes.InvalidCredentials: + return &provider.AuthenticateResponse{ + Status: status.NewPermissionDenied(ctx, v, "wrong password"), + }, nil + case errtypes.NotFound: + return &provider.AuthenticateResponse{ + Status: status.NewNotFound(ctx, "unknown client id"), + }, nil + default: err = errors.Wrap(err, "authsvc: error in Authenticate") - res := &provider.AuthenticateResponse{ + return &provider.AuthenticateResponse{ Status: status.NewUnauthenticated(ctx, err, "error authenticating user"), - } - return res, nil + }, nil } - log.Info().Msgf("user %s authenticated", u.String()) - res := &provider.AuthenticateResponse{ - Status: status.NewOK(ctx), - User: u, - } - return res, nil } diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 15a3113239..652ca4ee71 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -20,6 +20,7 @@ package gateway import ( "context" + "fmt" provider "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" @@ -53,18 +54,20 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest ClientSecret: req.ClientSecret, } res, err := c.Authenticate(ctx, authProviderReq) - if err != nil { - log.Err(err).Msgf("gateway: error calling Authenticate for type: %s", req.Type) + switch { + case err != nil: return &gateway.AuthenticateResponse{ - Status: status.NewUnauthenticated(ctx, err, "error authenticating request"), + Status: status.NewInternal(ctx, err, fmt.Sprintf("gateway: error calling Authenticate for type: %s", req.Type)), }, nil - } - - if res.Status.Code != rpc.Code_CODE_OK { + case res.Status.Code == rpc.Code_CODE_UNAUTHENTICATED, res.Status.Code == rpc.Code_CODE_NOT_FOUND: + // normal failures, no need to log + return &gateway.AuthenticateResponse{ + Status: res.Status, + }, nil + case res.Status.Code != rpc.Code_CODE_OK: err := status.NewErrorFromCode(res.Status.Code, "gateway") - log.Err(err).Msgf("error authenticating credentials to auth provider for type: %s", req.Type) return &gateway.AuthenticateResponse{ - Status: status.NewUnauthenticated(ctx, err, ""), + Status: status.NewInternal(ctx, err, fmt.Sprintf("error authenticating credentials to auth provider for type: %s", req.Type)), }, nil } diff --git a/internal/grpc/services/publicshareprovider/publicshareprovider.go b/internal/grpc/services/publicshareprovider/publicshareprovider.go index 8ca0801298..2f8a143deb 100644 --- a/internal/grpc/services/publicshareprovider/publicshareprovider.go +++ b/internal/grpc/services/publicshareprovider/publicshareprovider.go @@ -25,6 +25,7 @@ import ( link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/publicshare" "github.com/cs3org/reva/pkg/publicshare/manager/registry" "github.com/cs3org/reva/pkg/rgrpc" @@ -145,18 +146,29 @@ func (s *service) RemovePublicShare(ctx context.Context, req *link.RemovePublicS func (s *service) GetPublicShareByToken(ctx context.Context, req *link.GetPublicShareByTokenRequest) (*link.GetPublicShareByTokenResponse, error) { log := appctx.GetLogger(ctx) - log.Info().Msg("getting public share by token") + log.Debug().Msg("getting public share by token") // there are 2 passes here, and the second request has no password found, err := s.sm.GetPublicShareByToken(ctx, req.GetToken(), req.GetPassword()) - if err != nil { - return nil, err + switch v := err.(type) { + case nil: + return &link.GetPublicShareByTokenResponse{ + Status: status.NewOK(ctx), + Share: found, + }, nil + case errtypes.InvalidCredentials: + return &link.GetPublicShareByTokenResponse{ + Status: status.NewPermissionDenied(ctx, v, "wrong password"), + }, nil + case errtypes.NotFound: + return &link.GetPublicShareByTokenResponse{ + Status: status.NewNotFound(ctx, "unknown token"), + }, nil + default: + return &link.GetPublicShareByTokenResponse{ + Status: status.NewInternal(ctx, v, "unexpected error"), + }, nil } - - return &link.GetPublicShareByTokenResponse{ - Status: status.NewOK(ctx), - Share: found, - }, nil } func (s *service) GetPublicShare(ctx context.Context, req *link.GetPublicShareRequest) (*link.GetPublicShareResponse, error) { diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index 6f1d500eb4..4fc62ed16e 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -171,7 +171,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { w.WriteHeader(http.StatusNotFound) } - _, pass, hasBasicAuth := r.BasicAuth() + _, pass, _ := r.BasicAuth() token, _ := router.ShiftPath(r.URL.Path) authenticateRequest := gatewayv1beta1.AuthenticateRequest{ @@ -181,16 +181,18 @@ func (h *DavHandler) Handler(s *svc) http.Handler { } res, err := c.Authenticate(r.Context(), &authenticateRequest) - if err != nil { + switch { + case err != nil: w.WriteHeader(http.StatusInternalServerError) return - } - if res.Status.Code == rpcv1beta1.Code_CODE_UNAUTHENTICATED { - if hasBasicAuth { - w.WriteHeader(http.StatusUnauthorized) - } else { - w.WriteHeader(http.StatusNotFound) - } + case res.Status.Code == rpcv1beta1.Code_CODE_UNAUTHENTICATED: + w.WriteHeader(http.StatusUnauthorized) + return + case res.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND: + w.WriteHeader(http.StatusNotFound) + return + case res.Status.Code != rpcv1beta1.Code_CODE_OK: + w.WriteHeader(http.StatusInternalServerError) return } diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 691de73e5c..74378f6e26 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -627,6 +627,9 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // the permission set that is passed in needs to be copied so we don't accidentially overwrite the permission set of other entries func copyPermissionSet(p *provider.ResourcePermissions) *provider.ResourcePermissions { + if p == nil { + return nil + } return &provider.ResourcePermissions{ AddGrant: p.AddGrant, CreateContainer: p.CreateContainer, diff --git a/pkg/auth/manager/publicshares/publicshares.go b/pkg/auth/manager/publicshares/publicshares.go index 244e69877b..ce5a8fcb2e 100644 --- a/pkg/auth/manager/publicshares/publicshares.go +++ b/pkg/auth/manager/publicshares/publicshares.go @@ -23,9 +23,11 @@ import ( user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" userprovider "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" "github.com/cs3org/reva/pkg/auth" "github.com/cs3org/reva/pkg/auth/manager/registry" + "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" @@ -74,8 +76,15 @@ func (m *manager) Authenticate(ctx context.Context, token, secret string) (*user Token: token, Password: secret, }) - if err != nil { + switch { + case err != nil: return nil, err + case publicShareResponse.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND: + return nil, errtypes.NotFound(publicShareResponse.Status.Message) + case publicShareResponse.Status.Code == rpcv1beta1.Code_CODE_PERMISSION_DENIED: + return nil, errtypes.InvalidCredentials(publicShareResponse.Status.Message) + case publicShareResponse.Status.Code != rpcv1beta1.Code_CODE_OK: + return nil, errtypes.InternalError(publicShareResponse.Status.Message) } getUserResponse, err := gwConn.GetUser(ctx, &userprovider.GetUserRequest{ diff --git a/pkg/publicshare/manager/json/json.go b/pkg/publicshare/manager/json/json.go index 8248a1eb77..d190c39a61 100644 --- a/pkg/publicshare/manager/json/json.go +++ b/pkg/publicshare/manager/json/json.go @@ -41,6 +41,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/publicshare" "github.com/cs3org/reva/pkg/publicshare/manager/registry" "github.com/golang/protobuf/jsonpb" @@ -520,13 +521,13 @@ func (m *manager) GetPublicShareByToken(ctx context.Context, token, password str return local, nil } - return nil, errors.New("json: invalid password") + return nil, errtypes.InvalidCredentials("json: invalid password") } return local, nil } } - return nil, fmt.Errorf("share with token: `%v` not found", token) + return nil, errtypes.NotFound(fmt.Sprintf("share with token: `%v` not found", token)) } // randString is a helper to create tokens. It could be a token manager instead. diff --git a/pkg/publicshare/manager/memory/memory.go b/pkg/publicshare/manager/memory/memory.go index 6f3e8f1421..81c664983d 100644 --- a/pkg/publicshare/manager/memory/memory.go +++ b/pkg/publicshare/manager/memory/memory.go @@ -32,6 +32,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/publicshare" "github.com/cs3org/reva/pkg/publicshare/manager/registry" ) @@ -215,7 +216,7 @@ func (m *manager) GetPublicShareByToken(ctx context.Context, token string, passw if ps, ok := m.shares.Load(token); ok { return ps.(*link.PublicShare), nil } - return nil, errors.New("invalid token") + return nil, errtypes.NotFound("invalid token") } func randString(n int) string { diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.txt b/tests/acceptance/expected-failures-on-OCIS-storage.txt index 42881b906d..3565db3d16 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.txt +++ b/tests/acceptance/expected-failures-on-OCIS-storage.txt @@ -471,10 +471,6 @@ apiSharePublicLink1/createPublicLinkShare.feature:537 apiSharePublicLink1/createPublicLinkShare.feature:538 apiSharePublicLink1/createPublicLinkShare.feature:574 apiSharePublicLink1/createPublicLinkShare.feature:575 -# -apiSharePublicLink1/createPublicLinkShare.feature:592 -apiSharePublicLink1/createPublicLinkShare.feature:593 -# apiSharePublicLink1/createPublicLinkShare.feature:635 apiSharePublicLink1/createPublicLinkShare.feature:636 apiSharePublicLink1/createPublicLinkShare.feature:649 diff --git a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt index af4b6b0a81..7f96b34b02 100644 --- a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt +++ b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt @@ -391,9 +391,7 @@ apiSharePublicLink1/changingPublicLinkShare.feature:244 # https://github.com/owncloud/ocis/issues/723 upload-only public link does not refer to files-drop page, nor are the permissions enforced # apiSharePublicLink1/changingPublicLinkShare.feature:267 -apiSharePublicLink1/changingPublicLinkShare.feature:278 apiSharePublicLink1/changingPublicLinkShare.feature:289 -apiSharePublicLink1/changingPublicLinkShare.feature:300 apiSharePublicLink1/createPublicLinkShare.feature:34 apiSharePublicLink1/createPublicLinkShare.feature:35 apiSharePublicLink1/createPublicLinkShare.feature:183 @@ -425,8 +423,6 @@ apiSharePublicLink1/createPublicLinkShare.feature:156 # # https://github.com/owncloud/ocis-reva/issues/41 various sharing settings cannot be set # -apiSharePublicLink1/createPublicLinkShare.feature:389 -apiSharePublicLink1/createPublicLinkShare.feature:390 apiSharePublicLink1/createPublicLinkShare.feature:410 apiSharePublicLink1/createPublicLinkShare.feature:411 apiSharePublicLink1/createPublicLinkShare.feature:431 @@ -447,10 +443,6 @@ apiSharePublicLink1/createPublicLinkShare.feature:537 apiSharePublicLink1/createPublicLinkShare.feature:538 apiSharePublicLink1/createPublicLinkShare.feature:574 apiSharePublicLink1/createPublicLinkShare.feature:575 -# -apiSharePublicLink1/createPublicLinkShare.feature:592 -apiSharePublicLink1/createPublicLinkShare.feature:593 -# apiSharePublicLink1/createPublicLinkShare.feature:635 apiSharePublicLink1/createPublicLinkShare.feature:636 apiSharePublicLink1/createPublicLinkShare.feature:649 @@ -507,11 +499,6 @@ apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:158 apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:179 apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:180 # -# https://github.com/owncloud/ocis-reva/issues/292 Public link enforce permissions -# -apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:97 -apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:98 -# # https://github.com/owncloud/product/issues/272 [OCIS] old public webdav api doesnt works # apiSharePublicLink2/reShareAsPublicLinkToSharesOldDav.feature:30 @@ -547,12 +534,9 @@ apiSharePublicLink2/updatePublicLinkShare.feature:440 # # https://github.com/owncloud/ocis-reva/issues/292 Public link enforce permissions # -apiSharePublicLink2/updatePublicLinkShare.feature:461 -apiSharePublicLink2/updatePublicLinkShare.feature:462 apiSharePublicLink2/updatePublicLinkShare.feature:486 apiSharePublicLink2/updatePublicLinkShare.feature:487 apiSharePublicLink2/uploadToPublicLinkShare.feature:9 -apiSharePublicLink2/uploadToPublicLinkShare.feature:74 apiSharePublicLink2/uploadToPublicLinkShare.feature:83 apiSharePublicLink2/uploadToPublicLinkShare.feature:103 apiSharePublicLink2/uploadToPublicLinkShare.feature:121 @@ -1353,11 +1337,6 @@ apiShareOperationsToShares/gettingShares.feature:168 # apiSharePublicLink2/multilinkSharing.feature:181 # -# https://github.com/owncloud/ocis/issues/762 path and other information are not shown if a share does not have "read" permission -# -apiShareOperationsToShares/uploadToShare.feature:64 -apiShareOperationsToShares/uploadToShare.feature:65 -# # https://github.com/owncloud/product/issues/293 sharing with group not available # apiShareOperationsToShares/uploadToShare.feature:39 From 68875a70e17610ebe129543fee3a2e0db49bea2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 18 Dec 2020 15:53:15 +0000 Subject: [PATCH 20/41] not found token should return 403 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../grpc/services/gateway/storageprovider.go | 4 +- .../publicstorageprovider.go | 134 ++++++++++++------ internal/http/services/owncloud/ocdav/dav.go | 39 +++-- 3 files changed, 107 insertions(+), 70 deletions(-) diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 9d96bb4108..6303bb1d3e 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -936,7 +936,7 @@ func (s *svc) UnsetArbitraryMetadata(ctx context.Context, req *provider.UnsetArb c, err := s.find(ctx, req.Ref) if err != nil { return &provider.UnsetArbitraryMetadataResponse{ - Status: status.NewStatusFromErrType(ctx, "SetArbitraryMetadata ref="+req.Ref.String(), err), + Status: status.NewStatusFromErrType(ctx, "UnsetArbitraryMetadata ref="+req.Ref.String(), err), }, nil } @@ -1044,7 +1044,7 @@ func (s *svc) stat(ctx context.Context, req *provider.StatRequest) (*provider.St c, err := s.find(ctx, req.Ref) if err != nil { return &provider.StatResponse{ - Status: status.NewStatusFromErrType(ctx, "SetArbitraryMetadata ref="+req.Ref.String(), err), + Status: status.NewStatusFromErrType(ctx, "stat ref="+req.Ref.String(), err), }, nil } diff --git a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go index ac69fa54ed..f39d76b044 100644 --- a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go +++ b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go @@ -21,7 +21,6 @@ package publicstorageprovider import ( "context" "encoding/json" - "fmt" "path" "strings" @@ -133,16 +132,19 @@ func (s *service) InitiateFileDownload(ctx context.Context, req *provider.Initia return s.initiateFileDownload(ctx, req) } -func (s *service) translatePublicRefToCS3Ref(ctx context.Context, ref *provider.Reference) (*provider.Reference, string, *link.PublicShare, error) { +func (s *service) translatePublicRefToCS3Ref(ctx context.Context, ref *provider.Reference) (*provider.Reference, string, *link.PublicShare, *rpc.Status, error) { log := appctx.GetLogger(ctx) tkn, relativePath, err := s.unwrap(ctx, ref) if err != nil { - return nil, "", nil, err + return nil, "", nil, nil, err } - originalPath, sh, err := s.resolveToken(ctx, tkn) - if err != nil { - return nil, "", nil, err + originalPath, ls, st, err := s.resolveToken(ctx, tkn) + switch { + case err != nil: + return nil, "", nil, nil, err + case st != nil: + return nil, "", nil, st, nil } cs3Ref := &provider.Reference{ @@ -151,12 +153,12 @@ func (s *service) translatePublicRefToCS3Ref(ctx context.Context, ref *provider. log.Debug(). Interface("sourceRef", ref). Interface("cs3Ref", cs3Ref). - Interface("share", sh). + Interface("share", ls). Str("tkn", tkn). Str("originalPath", originalPath). Str("relativePath", relativePath). Msg("translatePublicRefToCS3Ref") - return cs3Ref, tkn, sh, nil + return cs3Ref, tkn, ls, nil, nil } // Both, t.dir and tokenPath paths need to be merged: @@ -167,11 +169,15 @@ func (s *service) translatePublicRefToCS3Ref(ctx context.Context, ref *provider. // end = /einstein/files/public-links/foldera/folderb/ func (s *service) initiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*provider.InitiateFileDownloadResponse, error) { - cs3Ref, _, sh, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) - if err != nil { + cs3Ref, _, ls, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) + switch { + case err != nil: return nil, err - } - if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.InitiateFileDownload { + case st != nil: + return &provider.InitiateFileDownloadResponse{ + Status: st, + }, nil + case ls.GetPermissions() == nil || !ls.GetPermissions().Permissions.InitiateFileDownload: return &provider.InitiateFileDownloadResponse{ Status: status.NewPermissionDenied(ctx, nil, "share does not grant InitiateFileDownload permission"), }, nil @@ -215,11 +221,15 @@ func (s *service) initiateFileDownload(ctx context.Context, req *provider.Initia } func (s *service) InitiateFileUpload(ctx context.Context, req *provider.InitiateFileUploadRequest) (*provider.InitiateFileUploadResponse, error) { - cs3Ref, _, sh, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) - if err != nil { + cs3Ref, _, ls, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) + switch { + case err != nil: return nil, err - } - if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.InitiateFileUpload { + case st != nil: + return &provider.InitiateFileUploadResponse{ + Status: st, + }, nil + case ls.GetPermissions() == nil || !ls.GetPermissions().Permissions.InitiateFileUpload: return &provider.InitiateFileUploadResponse{ Status: status.NewPermissionDenied(ctx, nil, "share does not grant InitiateFileUpload permission"), }, nil @@ -286,11 +296,15 @@ func (s *service) CreateContainer(ctx context.Context, req *provider.CreateConta trace.StringAttribute("ref", req.Ref.String()), ) - cs3Ref, _, sh, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) - if err != nil { + cs3Ref, _, ls, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) + switch { + case err != nil: return nil, err - } - if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.CreateContainer { + case st != nil: + return &provider.CreateContainerResponse{ + Status: st, + }, nil + case ls.GetPermissions() == nil || !ls.GetPermissions().Permissions.CreateContainer: return &provider.CreateContainerResponse{ Status: status.NewPermissionDenied(ctx, nil, "share does not grant CreateContainer permission"), }, nil @@ -321,11 +335,15 @@ func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*pro trace.StringAttribute("ref", req.Ref.String()), ) - cs3Ref, _, sh, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) - if err != nil { + cs3Ref, _, ls, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) + switch { + case err != nil: return nil, err - } - if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.Delete { + case st != nil: + return &provider.DeleteResponse{ + Status: st, + }, nil + case ls.GetPermissions() == nil || !ls.GetPermissions().Permissions.Delete: return &provider.DeleteResponse{ Status: status.NewPermissionDenied(ctx, nil, "share does not grant Delete permission"), }, nil @@ -357,19 +375,28 @@ func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provide trace.StringAttribute("destination", req.Destination.String()), ) - cs3RefSource, tknSource, sh, err := s.translatePublicRefToCS3Ref(ctx, req.Source) - if err != nil { + cs3RefSource, tknSource, ls, st, err := s.translatePublicRefToCS3Ref(ctx, req.Source) + switch { + case err != nil: return nil, err - } - if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.Move { + case st != nil: + return &provider.MoveResponse{ + Status: st, + }, nil + case ls.GetPermissions() == nil || !ls.GetPermissions().Permissions.Move: return &provider.MoveResponse{ Status: status.NewPermissionDenied(ctx, nil, "share does not grant Move permission"), }, nil } // FIXME: maybe there's a shortcut possible here using the source path - cs3RefDestination, tknDest, _, err := s.translatePublicRefToCS3Ref(ctx, req.Destination) - if err != nil { + cs3RefDestination, tknDest, _, st, err := s.translatePublicRefToCS3Ref(ctx, req.Destination) + switch { + case err != nil: return nil, err + case st != nil: + return &provider.MoveResponse{ + Status: st, + }, nil } if tknSource != tknDest { @@ -409,11 +436,15 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide return nil, err } - originalPath, ls, err := s.resolveToken(ctx, tkn) - if err != nil { + originalPath, ls, st, err := s.resolveToken(ctx, tkn) + switch { + case err != nil: return nil, err - } - if ls.GetPermissions() == nil || !ls.GetPermissions().Permissions.Stat { + case st != nil: + return &provider.StatResponse{ + Status: st, + }, nil + case ls.GetPermissions() == nil || !ls.GetPermissions().Permissions.Stat: return &provider.StatResponse{ Status: status.NewPermissionDenied(ctx, nil, "share does not grant Stat permission"), }, nil @@ -471,11 +502,16 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer return nil, err } - pathFromToken, sh, err := s.resolveToken(ctx, tkn) - if err != nil { + pathFromToken, ls, st, err := s.resolveToken(ctx, tkn) + switch { + case err != nil: return nil, err + case st != nil: + return &provider.ListContainerResponse{ + Status: st, + }, nil } - if sh.GetPermissions() == nil || !sh.GetPermissions().Permissions.ListContainer { + if ls.GetPermissions() == nil || !ls.GetPermissions().Permissions.ListContainer { return &provider.ListContainerResponse{ Status: status.NewPermissionDenied(ctx, nil, "share does not grant ListContainer permission"), }, nil @@ -498,7 +534,7 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer } for i := range listContainerR.Infos { - filterPermissions(listContainerR.Infos[i].PermissionSet, sh.GetPermissions().Permissions) + filterPermissions(listContainerR.Infos[i].PermissionSet, ls.GetPermissions().Permissions) listContainerR.Infos[i].Path = path.Join(s.mountPath, "/", tkn, relativePath, path.Base(listContainerR.Infos[i].Path)) } @@ -609,14 +645,14 @@ func (s *service) trimMountPrefix(fn string) (string, error) { if strings.HasPrefix(fn, s.mountPath) { return path.Join("/", strings.TrimPrefix(fn, s.mountPath)), nil } - return "", errors.New(fmt.Sprintf("path=%q does not belong to this storage provider mount path=%q"+fn, s.mountPath)) + return "", errors.Errorf("path=%q does not belong to this storage provider mount path=%q"+fn, s.mountPath) } // resolveToken returns the path and share for the publicly shared resource. -func (s *service) resolveToken(ctx context.Context, token string) (string, *link.PublicShare, error) { +func (s *service) resolveToken(ctx context.Context, token string) (string, *link.PublicShare, *rpc.Status, error) { driver, err := pool.GetGatewayServiceClient(s.conf.GatewayAddr) if err != nil { - return "", nil, err + return "", nil, nil, err } publicShareResponse, err := driver.GetPublicShare( @@ -629,16 +665,22 @@ func (s *service) resolveToken(ctx context.Context, token string) (string, *link }, }, ) - if err != nil { - return "", nil, err + switch { + case err != nil: + return "", nil, nil, err + case publicShareResponse.Status.Code != rpc.Code_CODE_OK: + return "", nil, publicShareResponse.Status, nil } pathRes, err := s.gateway.GetPath(ctx, &provider.GetPathRequest{ ResourceId: publicShareResponse.GetShare().GetResourceId(), }) - if err != nil { - return "", nil, err + switch { + case err != nil: + return "", nil, nil, err + case pathRes.Status.Code != rpc.Code_CODE_OK: + return "", nil, pathRes.Status, nil } - return pathRes.Path, publicShareResponse.GetShare(), nil + return pathRes.Path, publicShareResponse.GetShare(), nil, nil } diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index 4fc62ed16e..8a4e873cd7 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -202,24 +202,24 @@ func (h *DavHandler) Handler(s *svc) http.Handler { r = r.WithContext(ctx) + // the public share manager knew the token, but does the referenced target still exist? sRes, err := getTokenStatInfo(ctx, c, token) - if err != nil { + switch { + case err != nil: log.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) return - } - if sRes.Status.Code != rpc.Code_CODE_OK { - switch sRes.Status.Code { - case rpc.Code_CODE_NOT_FOUND: - log.Debug().Str("token", token).Interface("status", res.Status).Msg("resource not found") - w.WriteHeader(http.StatusNotFound) - case rpc.Code_CODE_PERMISSION_DENIED: - log.Debug().Str("token", token).Interface("status", res.Status).Msg("permission denied") - w.WriteHeader(http.StatusForbidden) - default: - log.Error().Str("token", token).Interface("status", res.Status).Msg("grpc stat request failed") - w.WriteHeader(http.StatusInternalServerError) - } + case sRes.Status.Code == rpc.Code_CODE_NOT_FOUND: + log.Debug().Str("token", token).Interface("status", res.Status).Msg("resource not found") + w.WriteHeader(http.StatusForbidden) // log the difference + return + case sRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED, sRes.Status.Code == rpc.Code_CODE_UNAUTHENTICATED: + log.Debug().Str("token", token).Interface("status", res.Status).Msg("permission denied") + w.WriteHeader(http.StatusForbidden) + return + case sRes.Status.Code != rpc.Code_CODE_OK: + log.Error().Str("token", token).Interface("status", res.Status).Msg("grpc stat request failed") + w.WriteHeader(http.StatusInternalServerError) return } log.Debug().Interface("statInfo", sRes.Info).Msg("Stat info from public link token path") @@ -239,12 +239,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { } func getTokenStatInfo(ctx context.Context, client gatewayv1beta1.GatewayAPIClient, token string) (*provider.StatResponse, error) { - ns := "/public" - - fn := path.Join(ns, token) - ref := &provider.Reference{ - Spec: &provider.Reference_Path{Path: fn}, - } - req := &provider.StatRequest{Ref: ref} - return client.Stat(ctx, req) + return client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ + Spec: &provider.Reference_Path{Path: path.Join("/public", token)}, + }}) } From 997ca40764058a4b6aa0b9e2c4f0b670c3c47b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 18 Dec 2020 16:04:59 +0000 Subject: [PATCH 21/41] guard against missing permissionset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- internal/http/services/owncloud/ocdav/propfind.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 74378f6e26..322e5a686d 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -281,7 +281,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } // files never have the create container permission - if md.Type == provider.ResourceType_RESOURCE_TYPE_FILE { + if md.Type == provider.ResourceType_RESOURCE_TYPE_FILE && md.PermissionSet != nil { md.PermissionSet = copyPermissionSet(md.PermissionSet) md.PermissionSet.CreateContainer = false } From 59c9f95ad5855fe20785d852352393d709934b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 21 Dec 2020 13:41:51 +0000 Subject: [PATCH 22/41] differentiate between oc:permissions and ocs:share-permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../http/services/owncloud/ocdav/propfind.go | 41 +++++++++++++------ .../services/owncloud/ocs/conversions/role.go | 5 ++- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 322e5a686d..02235027b0 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -280,8 +280,11 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide Propstat: []propstatXML{}, } + role := conversions.RoleFromResourcePermissions(md.PermissionSet) + // files never have the create container permission if md.Type == provider.ResourceType_RESOURCE_TYPE_FILE && md.PermissionSet != nil { + // we need to copy the permission set in case it was reused when passing it in while listing a collection md.PermissionSet = copyPermissionSet(md.PermissionSet) md.PermissionSet.CreateContainer = false } @@ -293,11 +296,6 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide if err != nil { sublog.Error().Err(err).Msg("could not unmarshal link json") } - // shared files never have the delete permission - if md.Type == provider.ResourceType_RESOURCE_TYPE_FILE { - // the permission set has already been copied above - md.PermissionSet.Delete = false - } } isShared := !isCurrentUserOwner(ctx, md.Owner) @@ -326,14 +324,13 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } if md.PermissionSet != nil { - r := conversions.RoleFromResourcePermissions(md.PermissionSet) - wdp := r.WebDAVPermissions( + wdp := role.WebDAVPermissions( md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, isShared, false, false, ) - sublog.Debug().Interface("role", r).Str("dav-permissions", wdp).Msg("converted PermissionSet") + sublog.Debug().Interface("role", role).Str("dav-permissions", wdp).Msg("converted PermissionSet") response.Propstat[0].Prop = append( response.Propstat[0].Prop, s.newProp("oc:permissions", wdp)) @@ -413,15 +410,23 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:id", "")) } case "permissions": // both + // oc:permissions take several char flags to indicate the permissions the user has on this node: + // D = delete + // NV = update (renameable moveable) + // W = update (files only) + // CK = create (folders only) + // S = Shared + // R = Shareable + // M = Mounted + // in contrast, the ocs:share-permissions further down below indicate clients the maximum permissions that can be granted if md.PermissionSet != nil { - r := conversions.RoleFromResourcePermissions(md.PermissionSet) - wdp := r.WebDAVPermissions( + wdp := role.WebDAVPermissions( md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, isShared, false, false, ) - sublog.Debug().Interface("role", r).Str("dav-permissions", wdp).Msg("converted PermissionSet") + sublog.Debug().Interface("role", role).Str("dav-permissions", wdp).Msg("converted PermissionSet") propstatOK.Prop = append( propstatOK.Prop, s.newProp("oc:permissions", wdp)) @@ -593,9 +598,21 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } case "http://open-collaboration-services.org/ns": switch pf.Prop[i].Local { + // ocs:share-permissions indicate clients the maximum permissions that can be granted: + // 1 = read + // 2 = write (update) + // 4 = create + // 8 = delete + // 16 = share + // shared files can never have the create or delete permission bit set case "share-permissions": if md.PermissionSet != nil { - perms := conversions.RoleFromResourcePermissions(md.PermissionSet).OCSPermissions() + perms := role.OCSPermissions() + // shared files cant have the create or delete permission set + if md.Type == provider.ResourceType_RESOURCE_TYPE_FILE { + perms &^= conversions.PermissionCreate + perms &^= conversions.PermissionDelete + } propstatOK.Prop = append(propstatOK.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, strconv.FormatUint(uint64(perms), 10))) } default: diff --git a/internal/http/services/owncloud/ocs/conversions/role.go b/internal/http/services/owncloud/ocs/conversions/role.go index 5ed0aa5b96..a9de8eb658 100644 --- a/internal/http/services/owncloud/ocs/conversions/role.go +++ b/internal/http/services/owncloud/ocs/conversions/role.go @@ -347,10 +347,13 @@ func NewLegacyRoleFromOCSPermissions(p Permissions) *Role { // RoleFromResourcePermissions tries to map cs3 resource permissions to a role func RoleFromResourcePermissions(rp *provider.ResourcePermissions) *Role { r := &Role{ - Name: RoleLegacy, + Name: RoleUnknown, ocsPermissions: PermissionInvalid, cS3ResourcePermissions: rp, } + if rp == nil { + return r + } if rp.ListContainer && rp.ListGrants && rp.ListFileVersions && From 98e5117f629d6920f487a434b98b2f2632f3eafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 21 Dec 2020 20:40:17 +0000 Subject: [PATCH 23/41] ace read flag grants GetPath MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- internal/http/services/owncloud/ocdav/propfind.go | 14 ++++++++++---- .../http/services/owncloud/ocs/conversions/role.go | 2 +- pkg/storage/utils/ace/ace.go | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 02235027b0..0ee3b5fcc9 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -416,7 +416,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // W = update (files only) // CK = create (folders only) // S = Shared - // R = Shareable + // R = Shareable (Reshare) // M = Mounted // in contrast, the ocs:share-permissions further down below indicate clients the maximum permissions that can be granted if md.PermissionSet != nil { @@ -488,8 +488,14 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // oc:size is also available on folders propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:size", size)) case "owner-id": // phoenix only - if md.Owner != nil && md.Owner.OpaqueId != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-id", s.xmlEscaped(md.Owner.OpaqueId))) + if md.Owner != nil { + if isCurrentUserOwner(ctx, md.Owner) { + u := ctxuser.ContextMustGetUser(ctx) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-id", s.xmlEscaped(u.Username))) + } else { + sublog.Debug().Msg("TODO fetch user username") + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-id", "")) + } } else { propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-id", "")) } @@ -531,7 +537,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide if md.Owner != nil { if isCurrentUserOwner(ctx, md.Owner) { u := ctxuser.ContextMustGetUser(ctx) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-display-name", u.Username)) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-display-name", u.DisplayName)) } else { sublog.Debug().Msg("TODO fetch user displayname") propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-display-name", "")) diff --git a/internal/http/services/owncloud/ocs/conversions/role.go b/internal/http/services/owncloud/ocs/conversions/role.go index a9de8eb658..87f4aff74b 100644 --- a/internal/http/services/owncloud/ocs/conversions/role.go +++ b/internal/http/services/owncloud/ocs/conversions/role.go @@ -93,7 +93,7 @@ func (r *Role) WebDAVPermissions(isDir, isShared, isMountpoint, isPublic bool) s var b strings.Builder //b.Grow(7) if r.ocsPermissions.Contain(PermissionDelete) { - fmt.Fprintf(&b, "D") + fmt.Fprintf(&b, "D") // TODO oc10 shows received shares as deletable } if r.ocsPermissions.Contain(PermissionWrite) { fmt.Fprintf(&b, "NV") diff --git a/pkg/storage/utils/ace/ace.go b/pkg/storage/utils/ace/ace.go index 3a900fb300..faf614082e 100644 --- a/pkg/storage/utils/ace/ace.go +++ b/pkg/storage/utils/ace/ace.go @@ -203,6 +203,7 @@ func (e *ACE) grantPermissionSet() *provider.ResourcePermissions { // r if strings.Contains(e.permissions, "r") { p.Stat = true + p.GetPath = true p.InitiateFileDownload = true p.ListContainer = true } @@ -259,7 +260,6 @@ func (e *ACE) grantPermissionSet() *provider.ResourcePermissions { } // ? - // TODO GetPath if strings.Contains(e.permissions, "q") { p.GetQuota = true } From c3a2fa5cfc085bc694b0e282522706365727517d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 21 Dec 2020 21:14:20 +0000 Subject: [PATCH 24/41] forward more error types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- internal/grpc/services/gateway/authprovider.go | 6 +++++- internal/http/services/owncloud/ocdav/dav.go | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 652ca4ee71..3c300ce64e 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -59,7 +59,11 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest return &gateway.AuthenticateResponse{ Status: status.NewInternal(ctx, err, fmt.Sprintf("gateway: error calling Authenticate for type: %s", req.Type)), }, nil - case res.Status.Code == rpc.Code_CODE_UNAUTHENTICATED, res.Status.Code == rpc.Code_CODE_NOT_FOUND: + case res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED: + fallthrough + case res.Status.Code == rpc.Code_CODE_UNAUTHENTICATED: + fallthrough + case res.Status.Code == rpc.Code_CODE_NOT_FOUND: // normal failures, no need to log return &gateway.AuthenticateResponse{ Status: res.Status, diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index 8a4e873cd7..065888ca86 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -185,6 +185,8 @@ func (h *DavHandler) Handler(s *svc) http.Handler { case err != nil: w.WriteHeader(http.StatusInternalServerError) return + case res.Status.Code == rpcv1beta1.Code_CODE_PERMISSION_DENIED: + fallthrough case res.Status.Code == rpcv1beta1.Code_CODE_UNAUTHENTICATED: w.WriteHeader(http.StatusUnauthorized) return @@ -213,7 +215,9 @@ func (h *DavHandler) Handler(s *svc) http.Handler { log.Debug().Str("token", token).Interface("status", res.Status).Msg("resource not found") w.WriteHeader(http.StatusForbidden) // log the difference return - case sRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED, sRes.Status.Code == rpc.Code_CODE_UNAUTHENTICATED: + case sRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED: + fallthrough + case sRes.Status.Code == rpc.Code_CODE_UNAUTHENTICATED: log.Debug().Str("token", token).Interface("status", res.Status).Msg("permission denied") w.WriteHeader(http.StatusForbidden) return From 498fa3f55e82e1fc27397532b6678547e5695025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 21 Dec 2020 22:04:54 +0000 Subject: [PATCH 25/41] update expected failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- tests/acceptance/expected-failures-on-EOS-storage.txt | 8 -------- .../acceptance/expected-failures-on-OCIS-storage.txt | 11 ----------- .../expected-failures-on-OWNCLOUD-storage.txt | 4 ---- 3 files changed, 23 deletions(-) diff --git a/tests/acceptance/expected-failures-on-EOS-storage.txt b/tests/acceptance/expected-failures-on-EOS-storage.txt index 529c698a76..ae9b71c9e4 100644 --- a/tests/acceptance/expected-failures-on-EOS-storage.txt +++ b/tests/acceptance/expected-failures-on-EOS-storage.txt @@ -781,14 +781,6 @@ apiWebdavProperties2/getFileProperties.feature:402 apiWebdavProperties2/getFileProperties.feature:403 # # https://github.com/owncloud/ocis-reva/issues/217 Some failing tests with Webdav custom properties -apiWebdavProperties2/getFileProperties.feature:415 -apiWebdavProperties2/getFileProperties.feature:416 -# -# https://github.com/owncloud/ocis-reva/issues/217 Some failing tests with Webdav custom properties -apiWebdavProperties2/getFileProperties.feature:428 -apiWebdavProperties2/getFileProperties.feature:429 -# -# https://github.com/owncloud/ocis-reva/issues/217 Some failing tests with Webdav custom properties apiWebdavProperties2/getFileProperties.feature:441 apiWebdavProperties2/getFileProperties.feature:442 # diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.txt b/tests/acceptance/expected-failures-on-OCIS-storage.txt index 3565db3d16..c3c1ab8765 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.txt +++ b/tests/acceptance/expected-failures-on-OCIS-storage.txt @@ -369,11 +369,6 @@ apiShareOperationsToShares/gettingSharesSharedFilteredEmpty.feature:61 apiShareOperationsToShares/gettingSharesSharedFilteredEmpty.feature:79 apiShareOperationsToShares/gettingSharesSharedFilteredEmpty.feature:80 # -# https://github.com/owncloud/ocis-reva/issues/47 cannot get ocs:share-permissions via WebDAV -# -apiShareOperationsToShares/getWebDAVSharePermissions.feature:23 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:24 -# # https://github.com/owncloud/product/issues/203 file_target in share response # apiShareOperationsToShares/gettingShares.feature:167 @@ -1200,10 +1195,6 @@ apiWebdavProperties2/getFileProperties.feature:327 apiWebdavProperties2/getFileProperties.feature:328 apiWebdavProperties2/getFileProperties.feature:376 apiWebdavProperties2/getFileProperties.feature:377 -apiWebdavProperties2/getFileProperties.feature:415 -apiWebdavProperties2/getFileProperties.feature:416 -apiWebdavProperties2/getFileProperties.feature:428 -apiWebdavProperties2/getFileProperties.feature:429 apiWebdavProperties2/getFileProperties.feature:441 apiWebdavProperties2/getFileProperties.feature:442 apiWebdavProperties2/getFileProperties.feature:454 @@ -1807,8 +1798,6 @@ apiShareOperationsToShares/getWebDAVSharePermissions.feature:129 apiShareOperationsToShares/getWebDAVSharePermissions.feature:130 apiShareOperationsToShares/getWebDAVSharePermissions.feature:177 apiShareOperationsToShares/getWebDAVSharePermissions.feature:178 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:191 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:192 apiShareOperationsToShares/getWebDAVSharePermissions.feature:212 apiShareOperationsToShares/getWebDAVSharePermissions.feature:213 apiShareOperationsToShares/getWebDAVSharePermissions.feature:247 diff --git a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt index 7f96b34b02..bd823c45e2 100644 --- a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt +++ b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt @@ -1205,10 +1205,6 @@ apiWebdavProperties2/getFileProperties.feature:327 apiWebdavProperties2/getFileProperties.feature:328 apiWebdavProperties2/getFileProperties.feature:376 apiWebdavProperties2/getFileProperties.feature:377 -apiWebdavProperties2/getFileProperties.feature:415 -apiWebdavProperties2/getFileProperties.feature:416 -apiWebdavProperties2/getFileProperties.feature:428 -apiWebdavProperties2/getFileProperties.feature:429 apiWebdavProperties2/getFileProperties.feature:441 apiWebdavProperties2/getFileProperties.feature:442 apiWebdavProperties2/getFileProperties.feature:454 From 9500fbc8ad6124aa985e1e67252b4b9eab5412fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Tue, 22 Dec 2020 13:39:34 +0000 Subject: [PATCH 26/41] return missing link target as 404 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- internal/http/services/owncloud/ocdav/dav.go | 14 +++++++------- ...ePublicLink2-uploadToPublicLinkShare.feature | 17 ----------------- 2 files changed, 7 insertions(+), 24 deletions(-) delete mode 100644 tests/acceptance/features/apiOcisSpecific/apiSharePublicLink2-uploadToPublicLinkShare.feature diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index 065888ca86..0cb485c61b 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -185,11 +185,11 @@ func (h *DavHandler) Handler(s *svc) http.Handler { case err != nil: w.WriteHeader(http.StatusInternalServerError) return - case res.Status.Code == rpcv1beta1.Code_CODE_PERMISSION_DENIED: - fallthrough case res.Status.Code == rpcv1beta1.Code_CODE_UNAUTHENTICATED: w.WriteHeader(http.StatusUnauthorized) return + case res.Status.Code == rpcv1beta1.Code_CODE_PERMISSION_DENIED: + fallthrough case res.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND: w.WriteHeader(http.StatusNotFound) return @@ -211,15 +211,15 @@ func (h *DavHandler) Handler(s *svc) http.Handler { log.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) return + case sRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED: + fallthrough case sRes.Status.Code == rpc.Code_CODE_NOT_FOUND: log.Debug().Str("token", token).Interface("status", res.Status).Msg("resource not found") - w.WriteHeader(http.StatusForbidden) // log the difference + w.WriteHeader(http.StatusNotFound) // log the difference return - case sRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED: - fallthrough case sRes.Status.Code == rpc.Code_CODE_UNAUTHENTICATED: - log.Debug().Str("token", token).Interface("status", res.Status).Msg("permission denied") - w.WriteHeader(http.StatusForbidden) + log.Debug().Str("token", token).Interface("status", res.Status).Msg("unauthorized") + w.WriteHeader(http.StatusUnauthorized) return case sRes.Status.Code != rpc.Code_CODE_OK: log.Error().Str("token", token).Interface("status", res.Status).Msg("grpc stat request failed") diff --git a/tests/acceptance/features/apiOcisSpecific/apiSharePublicLink2-uploadToPublicLinkShare.feature b/tests/acceptance/features/apiOcisSpecific/apiSharePublicLink2-uploadToPublicLinkShare.feature deleted file mode 100644 index d706bbf7d0..0000000000 --- a/tests/acceptance/features/apiOcisSpecific/apiSharePublicLink2-uploadToPublicLinkShare.feature +++ /dev/null @@ -1,17 +0,0 @@ -@api @files_sharing-app-required @public_link_share-feature-required @skipOnOcis-EOS-Storage @issue-ocis-reva-315 @issue-ocis-reva-316 - -Feature: upload to a public link share - - Background: - Given user "Alice" has been created with default attributes and skeleton files - - @issue-ocis-reva-290 - # after fixing all issues delete this Scenario and use the one from oC10 core - Scenario: Uploading file to a public upload-only share that was deleted does not work - Given the administrator has enabled DAV tech_preview - And user "Alice" has created a public link share with settings - | path | FOLDER | - | permissions | create | - When user "Alice" deletes file "/FOLDER" using the WebDAV API - And the public uploads file "does-not-matter.txt" with content "does not matter" using the new public WebDAV API - Then the HTTP status code should be "403" From 3fda8682ec69297cf9d17faa08311f3630f63fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 4 Jan 2021 09:18:20 +0000 Subject: [PATCH 27/41] fix permission check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../owncloud/ocs/conversions/permissions.go | 2 +- .../ocs/conversions/permissions_test.go | 49 +++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/internal/http/services/owncloud/ocs/conversions/permissions.go b/internal/http/services/owncloud/ocs/conversions/permissions.go index e43c1e3a57..4d860fb694 100644 --- a/internal/http/services/owncloud/ocs/conversions/permissions.go +++ b/internal/http/services/owncloud/ocs/conversions/permissions.go @@ -60,5 +60,5 @@ func NewPermissions(val int) (Permissions, error) { // Contain tests if the permissions contain another one. func (p Permissions) Contain(other Permissions) bool { - return p&other != 0 + return p&other == other } diff --git a/internal/http/services/owncloud/ocs/conversions/permissions_test.go b/internal/http/services/owncloud/ocs/conversions/permissions_test.go index e54eaa4a9a..6604b8deb9 100644 --- a/internal/http/services/owncloud/ocs/conversions/permissions_test.go +++ b/internal/http/services/owncloud/ocs/conversions/permissions_test.go @@ -45,7 +45,7 @@ func TestNewPermissionsWithInvalidValueShouldFail(t *testing.T) { } } -func TestContain(t *testing.T) { +func TestContainPermissionAll(t *testing.T) { table := map[int]Permissions{ 1: PermissionRead, 2: PermissionWrite, @@ -55,13 +55,56 @@ func TestContain(t *testing.T) { 31: PermissionAll, } - for key, value := range table { - p, _ := NewPermissions(key) + p, _ := NewPermissions(31) // all permissions should contain all other permissions + for _, value := range table { if !p.Contain(value) { t.Errorf("permissions %d should contain %d", p, value) } } } +func TestContainPermissionRead(t *testing.T) { + table := map[int]Permissions{ + 2: PermissionWrite, + 4: PermissionCreate, + 8: PermissionDelete, + 16: PermissionShare, + 31: PermissionAll, + } + + p, _ := NewPermissions(1) // read permission should not contain any other permissions + if !p.Contain(PermissionRead) { + t.Errorf("permissions %d should contain %d", p, PermissionRead) + } + for _, value := range table { + if p.Contain(value) { + t.Errorf("permissions %d should not contain %d", p, value) + } + } +} + +func TestContainPermissionCustom(t *testing.T) { + table := map[int]Permissions{ + 2: PermissionWrite, + 8: PermissionDelete, + 31: PermissionAll, + } + + p, _ := NewPermissions(21) // read, create & share permission + if !p.Contain(PermissionRead) { + t.Errorf("permissions %d should contain %d", p, PermissionRead) + } + if !p.Contain(PermissionCreate) { + t.Errorf("permissions %d should contain %d", p, PermissionCreate) + } + if !p.Contain(PermissionShare) { + t.Errorf("permissions %d should contain %d", p, PermissionShare) + } + for _, value := range table { + if p.Contain(value) { + t.Errorf("permissions %d should not contain %d", p, value) + } + } +} func TestContainWithMultiplePermissions(t *testing.T) { table := map[int][]Permissions{ From f3f558aa72501fcb1514ad0158d864a653146e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 4 Jan 2021 09:19:40 +0000 Subject: [PATCH 28/41] treat permission denied as 401 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- internal/http/services/owncloud/ocdav/dav.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index 0cb485c61b..c6e95af51c 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -185,11 +185,11 @@ func (h *DavHandler) Handler(s *svc) http.Handler { case err != nil: w.WriteHeader(http.StatusInternalServerError) return + case res.Status.Code == rpcv1beta1.Code_CODE_PERMISSION_DENIED: + fallthrough case res.Status.Code == rpcv1beta1.Code_CODE_UNAUTHENTICATED: w.WriteHeader(http.StatusUnauthorized) return - case res.Status.Code == rpcv1beta1.Code_CODE_PERMISSION_DENIED: - fallthrough case res.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND: w.WriteHeader(http.StatusNotFound) return From 392a83f3c77ec2553d60f15ddde8e4a02c14f385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 4 Jan 2021 09:22:05 +0000 Subject: [PATCH 29/41] update local test configs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../oc-integration-tests/local/frontend.toml | 2 +- tests/oc-integration-tests/local/gateway.toml | 2 +- .../local/storage-home.toml | 25 ++++++++++-------- .../{storage-oc.toml => storage-users.toml} | 26 ++++++++++--------- 4 files changed, 30 insertions(+), 25 deletions(-) rename tests/oc-integration-tests/local/{storage-oc.toml => storage-users.toml} (66%) diff --git a/tests/oc-integration-tests/local/frontend.toml b/tests/oc-integration-tests/local/frontend.toml index d0e377b06f..9d74dc17a8 100644 --- a/tests/oc-integration-tests/local/frontend.toml +++ b/tests/oc-integration-tests/local/frontend.toml @@ -47,7 +47,7 @@ chunk_folder = "/var/tmp/reva/chunks" # for eos we need to rewrite the path # TODO strip the username from the path so the CS3 namespace can be mounted # at the files/ endpoint? what about migration? separate reva instance -files_namespace = "/oc" +files_namespace = "/users" # similar to the dav/files endpoint we can configure a prefix for the old webdav endpoint # we use the old webdav endpoint to present the cs3 namespace diff --git a/tests/oc-integration-tests/local/gateway.toml b/tests/oc-integration-tests/local/gateway.toml index f78238b0ed..8adea69f59 100644 --- a/tests/oc-integration-tests/local/gateway.toml +++ b/tests/oc-integration-tests/local/gateway.toml @@ -58,7 +58,7 @@ home_provider = "/home" "/home" = "localhost:12000" # mount a storage provider without a path wrapper for direct access to users. -"/oc" = "localhost:11000" +"/users" = "localhost:11000" "123e4567-e89b-12d3-a456-426655440000" = "localhost:11000" # another mount point might be "/projects/" diff --git a/tests/oc-integration-tests/local/storage-home.toml b/tests/oc-integration-tests/local/storage-home.toml index 44502b2b71..60e5b15ccf 100644 --- a/tests/oc-integration-tests/local/storage-home.toml +++ b/tests/oc-integration-tests/local/storage-home.toml @@ -22,29 +22,32 @@ address = "0.0.0.0:12000" # this is where clients can find it # the context path wrapper reads tho username from the context and prefixes the relative storage path with it [grpc.services.storageprovider] -driver = "owncloud" +driver = "ocis" mount_path = "/home" mount_id = "123e4567-e89b-12d3-a456-426655440000" expose_data_server = true -data_server_url = "http://revad-services:12001/data" +data_server_url = "http://localhost:12001/data" enable_home_creation = true -[grpc.services.storageprovider.drivers.owncloud] -datadirectory = "/var/tmp/reva/data" +[grpc.services.storageprovider.drivers.ocis] +root = "/var/tmp/reva/data" enable_home = true -redis = "localhost:6379" -userprovidersvc = "localhost:18000" +treetime_accounting = true +treesize_accounting = true +#user_layout = +# do we need owner for users? +#owner = 95cb8724-03b2-11eb-a0a6-c33ef8ef53ad [http] address = "0.0.0.0:12001" [http.services.dataprovider] -driver = "owncloud" +driver = "ocis" temp_folder = "/var/tmp/reva/tmp" -[http.services.dataprovider.drivers.owncloud] -datadirectory = "/var/tmp/reva/data" +[http.services.dataprovider.drivers.ocis] +root = "/var/tmp/reva/data" enable_home = true -redis = "localhost:6379" -userprovidersvc = "localhost:18000" +treetime_accounting = true +treesize_accounting = true diff --git a/tests/oc-integration-tests/local/storage-oc.toml b/tests/oc-integration-tests/local/storage-users.toml similarity index 66% rename from tests/oc-integration-tests/local/storage-oc.toml rename to tests/oc-integration-tests/local/storage-users.toml index a7d1e3d84d..c2d4d0abf4 100644 --- a/tests/oc-integration-tests/local/storage-oc.toml +++ b/tests/oc-integration-tests/local/storage-users.toml @@ -14,25 +14,27 @@ address = "0.0.0.0:11000" # This is a storage provider that grants direct access to the wrapped storage # we have a locally running dataprovider [grpc.services.storageprovider] -driver = "owncloud" -mount_path = "/oc" +driver = "ocis" +mount_path = "/users" mount_id = "123e4567-e89b-12d3-a456-426655440000" expose_data_server = true -data_server_url = "http://revad-services:11001/data" +data_server_url = "http://localhost:11001/data" -[grpc.services.storageprovider.drivers.owncloud] -datadirectory = "/var/tmp/reva/data" -redis = "localhost:6379" -userprovidersvc = "localhost:18000" +[grpc.services.storageprovider.drivers.ocis] +root = "/var/tmp/reva/data" +enable_home = false +treetime_accounting = true +treesize_accounting = true [http] address = "0.0.0.0:11001" [http.services.dataprovider] -driver = "owncloud" +driver = "ocis" temp_folder = "/var/tmp/reva/tmp" -[http.services.dataprovider.drivers.owncloud] -datadirectory = "/var/tmp/reva/data" -redis = "localhost:6379" -userprovidersvc = "localhost:18000" +[http.services.dataprovider.drivers.ocis] +root = "/var/tmp/reva/data" +enable_home = false +treetime_accounting = true +treesize_accounting = true From d85b5b6618819a61810473fd8fe4a7ff779c42ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 4 Jan 2021 10:09:17 +0000 Subject: [PATCH 30/41] different default permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../handlers/apps/sharing/shares/shares.go | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index ee0b68f087..610ad2ed7e 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -195,24 +195,31 @@ func (h *Handler) createShare(w http.ResponseWriter, r *http.Request) { return } - // check the share permissions do not extend the user permissions - if !h.validatePermissions(w, r, statRes.Info.PermissionSet) { - return - } - switch shareType { case int(conversions.ShareTypeUser): + // user permissions default to coowner + if !h.validatePermissions(w, r, statRes.Info.PermissionSet, conversions.NewCoownerRole().OCSPermissions()) { + return + } h.createUserShare(w, r, statRes.Info) case int(conversions.ShareTypePublicLink): + // public links default to read only + if !h.validatePermissions(w, r, statRes.Info.PermissionSet, conversions.NewViewerRole().OCSPermissions()) { + return + } h.createPublicLinkShare(w, r, statRes.Info) case int(conversions.ShareTypeFederatedCloudShare): + // federated shares default to read only + if !h.validatePermissions(w, r, statRes.Info.PermissionSet, conversions.NewViewerRole().OCSPermissions()) { + return + } h.createFederatedCloudShare(w, r, statRes.Info) default: response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "unknown share type", nil) } } -func (h *Handler) validatePermissions(w http.ResponseWriter, r *http.Request, statPerms *provider.ResourcePermissions) bool { +func (h *Handler) validatePermissions(w http.ResponseWriter, r *http.Request, statPerms *provider.ResourcePermissions, defaultPermissions conversions.Permissions) bool { // 1. we start without permissions var reqPermissions conversions.Permissions @@ -227,7 +234,9 @@ func (h *Handler) validatePermissions(w http.ResponseWriter, r *http.Request, st pval := r.FormValue("permissions") if pval == "" { // default is read permissions / role viewer - reqPermissions = conversions.NewCoownerRole().OCSPermissions() + // TODO default link vs user share + //reqPermissions = conversions.NewCoownerRole().OCSPermissions() + reqPermissions = defaultPermissions } else { pint, err := strconv.Atoi(pval) if err != nil { From d6321bc91b8fdd4ce6cec58c602230cbdee89dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 4 Jan 2021 13:13:35 +0000 Subject: [PATCH 31/41] handle file individual share permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../handlers/apps/sharing/shares/shares.go | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index 610ad2ed7e..2bc2a2b4e6 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -195,31 +195,34 @@ func (h *Handler) createShare(w http.ResponseWriter, r *http.Request) { return } + // check user has share permissions + if !conversions.RoleFromResourcePermissions(statRes.Info.PermissionSet).OCSPermissions().Contain(conversions.PermissionShare) { + response.WriteOCSError(w, r, http.StatusNotFound, "No share permission", nil) + return + } + switch shareType { case int(conversions.ShareTypeUser): - // user permissions default to coowner - if !h.validatePermissions(w, r, statRes.Info.PermissionSet, conversions.NewCoownerRole().OCSPermissions()) { - return + // user collaborations default to coowner + if h.validatePermissions(w, r, statRes.Info, conversions.NewCoownerRole().OCSPermissions()) { + h.createUserShare(w, r, statRes.Info) } - h.createUserShare(w, r, statRes.Info) case int(conversions.ShareTypePublicLink): // public links default to read only - if !h.validatePermissions(w, r, statRes.Info.PermissionSet, conversions.NewViewerRole().OCSPermissions()) { - return + if h.validatePermissions(w, r, statRes.Info, conversions.NewViewerRole().OCSPermissions()) { + h.createPublicLinkShare(w, r, statRes.Info) } - h.createPublicLinkShare(w, r, statRes.Info) case int(conversions.ShareTypeFederatedCloudShare): // federated shares default to read only - if !h.validatePermissions(w, r, statRes.Info.PermissionSet, conversions.NewViewerRole().OCSPermissions()) { - return + if h.validatePermissions(w, r, statRes.Info, conversions.NewViewerRole().OCSPermissions()) { + h.createFederatedCloudShare(w, r, statRes.Info) } - h.createFederatedCloudShare(w, r, statRes.Info) default: response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "unknown share type", nil) } } -func (h *Handler) validatePermissions(w http.ResponseWriter, r *http.Request, statPerms *provider.ResourcePermissions, defaultPermissions conversions.Permissions) bool { +func (h *Handler) validatePermissions(w http.ResponseWriter, r *http.Request, ri *provider.ResourceInfo, defaultPermissions conversions.Permissions) bool { // 1. we start without permissions var reqPermissions conversions.Permissions @@ -255,7 +258,13 @@ func (h *Handler) validatePermissions(w http.ResponseWriter, r *http.Request, st } } - existingPermissions := conversions.RoleFromResourcePermissions(statPerms).OCSPermissions() + if ri.Type == provider.ResourceType_RESOURCE_TYPE_FILE { + // Single file shares should never have delete or create permissions + reqPermissions &^= conversions.PermissionCreate + reqPermissions &^= conversions.PermissionDelete + } + + existingPermissions := conversions.RoleFromResourcePermissions(ri.PermissionSet).OCSPermissions() if !existingPermissions.Contain(reqPermissions) { response.WriteOCSError(w, r, http.StatusNotFound, "Cannot set the requested share permissions", nil) return false From 5e8fa195e11ba116242eb0afc1a318f49fcf7689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 4 Jan 2021 13:33:39 +0000 Subject: [PATCH 32/41] update unexpected passes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../expected-failures-on-OCIS-storage.txt | 66 ------------------- .../expected-failures-on-OWNCLOUD-storage.txt | 9 --- 2 files changed, 75 deletions(-) diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.txt b/tests/acceptance/expected-failures-on-OCIS-storage.txt index c3c1ab8765..795b2ab7fe 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.txt +++ b/tests/acceptance/expected-failures-on-OCIS-storage.txt @@ -509,8 +509,6 @@ apiSharePublicLink2/updatePublicLinkShare.feature:285 # # https://github.com/owncloud/product/issues/270 [OCIS] share permissions not enforced # -apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:77 -apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:78 apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:157 apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:158 apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:179 @@ -570,8 +568,6 @@ apiSharePublicLink2/uploadToPublicLinkShare.feature:273 # # https://github.com/owncloud/ocis-reva/issues/290 Accessing non-existing public link should return 404, not 500 # -apiSharePublicLink2/uploadToPublicLinkShare.feature:62 -apiSharePublicLink2/uploadToPublicLinkShare.feature:63 apiSharePublicLink2/uploadToPublicLinkShare.feature:66 # # https://github.com/owncloud/ocis-reva/issues/195 Set quota over settings @@ -592,10 +588,6 @@ apiSharePublicLink2/uploadToPublicLinkShare.feature:255 # # https://github.com/owncloud/product/issues/265 Resharing does not work with ocis storage # -apiShareReshareToShares1/reShare.feature:24 -apiShareReshareToShares1/reShare.feature:25 -apiShareReshareToShares1/reShare.feature:39 -apiShareReshareToShares1/reShare.feature:40 apiShareReshareToShares1/reShare.feature:56 apiShareReshareToShares1/reShare.feature:57 apiShareReshareToShares1/reShare.feature:72 @@ -604,12 +596,6 @@ apiShareReshareToShares1/reShare.feature:88 apiShareReshareToShares1/reShare.feature:89 apiShareReshareToShares1/reShare.feature:104 apiShareReshareToShares1/reShare.feature:105 -apiShareReshareToShares1/reShare.feature:120 -apiShareReshareToShares1/reShare.feature:121 -apiShareReshareToShares1/reShare.feature:122 -apiShareReshareToShares1/reShare.feature:123 -apiShareReshareToShares1/reShare.feature:124 -apiShareReshareToShares1/reShare.feature:125 apiShareReshareToShares1/reShare.feature:159 apiShareReshareToShares1/reShare.feature:160 apiShareReshareToShares1/reShare.feature:161 @@ -626,32 +612,6 @@ apiShareReshareToShares1/reShare.feature:171 apiShareReshareToShares1/reShare.feature:172 apiShareReshareToShares1/reShare.feature:173 apiShareReshareToShares1/reShare.feature:174 -apiShareReshareToShares1/reShare.feature:189 -apiShareReshareToShares1/reShare.feature:190 -apiShareReshareToShares1/reShare.feature:191 -apiShareReshareToShares1/reShare.feature:192 -apiShareReshareToShares1/reShare.feature:193 -apiShareReshareToShares1/reShare.feature:194 -apiShareReshareToShares1/reShare.feature:195 -apiShareReshareToShares1/reShare.feature:196 -apiShareReshareToShares1/reShare.feature:197 -apiShareReshareToShares1/reShare.feature:198 -apiShareReshareToShares1/reShare.feature:199 -apiShareReshareToShares1/reShare.feature:200 -apiShareReshareToShares1/reShare.feature:210 -apiShareReshareToShares1/reShare.feature:211 -apiShareReshareToShares1/reShare.feature:212 -apiShareReshareToShares1/reShare.feature:213 -apiShareReshareToShares1/reShare.feature:228 -apiShareReshareToShares1/reShare.feature:229 -apiShareReshareToShares1/reShare.feature:230 -apiShareReshareToShares1/reShare.feature:231 -apiShareReshareToShares1/reShare.feature:232 -apiShareReshareToShares1/reShare.feature:233 -apiShareReshareToShares1/reShare.feature:237 -apiShareReshareToShares1/reShare.feature:238 -apiShareReshareToShares1/reShare.feature:239 -apiShareReshareToShares1/reShare.feature:240 apiShareReshareToShares3/reShareWithExpiryDate.feature:365 apiShareReshareToShares3/reShareWithExpiryDate.feature:366 apiShareReshareToShares3/reShareWithExpiryDate.feature:367 @@ -700,32 +660,6 @@ apiShareReshareToShares2/reShareDisabled.feature:40 # # https://github.com/owncloud/product/issues/270 share permissions are not enforced # -apiShareReshareToShares2/reShareSubfolder.feature:47 -apiShareReshareToShares2/reShareSubfolder.feature:48 -apiShareReshareToShares2/reShareSubfolder.feature:49 -apiShareReshareToShares2/reShareSubfolder.feature:50 -apiShareReshareToShares2/reShareSubfolder.feature:51 -apiShareReshareToShares2/reShareSubfolder.feature:52 -apiShareReshareToShares2/reShareSubfolder.feature:53 -apiShareReshareToShares2/reShareSubfolder.feature:54 -apiShareReshareToShares2/reShareSubfolder.feature:55 -apiShareReshareToShares2/reShareSubfolder.feature:56 -apiShareReshareToShares2/reShareSubfolder.feature:57 -apiShareReshareToShares2/reShareSubfolder.feature:58 -apiShareReshareToShares2/reShareSubfolder.feature:68 -apiShareReshareToShares2/reShareSubfolder.feature:69 -apiShareReshareToShares2/reShareSubfolder.feature:70 -apiShareReshareToShares2/reShareSubfolder.feature:71 -apiShareReshareToShares2/reShareSubfolder.feature:73 -apiShareReshareToShares2/reShareSubfolder.feature:74 -apiShareReshareToShares2/reShareSubfolder.feature:75 -apiShareReshareToShares2/reShareSubfolder.feature:76 -apiShareReshareToShares2/reShareSubfolder.feature:77 -apiShareReshareToShares2/reShareSubfolder.feature:78 -apiShareReshareToShares2/reShareSubfolder.feature:82 -apiShareReshareToShares2/reShareSubfolder.feature:83 -apiShareReshareToShares2/reShareSubfolder.feature:84 -apiShareReshareToShares2/reShareSubfolder.feature:85 apiShareManagementToShares/mergeShare.feature:24 apiShareManagementToShares/mergeShare.feature:52 apiShareManagementToShares/mergeShare.feature:79 diff --git a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt index bd823c45e2..e5591349a8 100644 --- a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt +++ b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt @@ -355,11 +355,6 @@ apiShareOperationsToShares/gettingSharesSharedFilteredEmpty.feature:61 apiShareOperationsToShares/gettingSharesSharedFilteredEmpty.feature:79 apiShareOperationsToShares/gettingSharesSharedFilteredEmpty.feature:80 # -# https://github.com/owncloud/ocis-reva/issues/47 cannot get ocs:share-permissions via WebDAV -# -apiShareOperationsToShares/getWebDAVSharePermissions.feature:23 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:24 -# # https://github.com/owncloud/ocis-reva/issues/282 Split old public API webdav tests from new public webdav tests # https://github.com/owncloud/ocis-reva/issues/292 Public link enforce permissions # https://github.com/owncloud/ocis/issues/187 Previews via webDAV API tests fail on OCIS @@ -554,8 +549,6 @@ apiSharePublicLink2/uploadToPublicLinkShare.feature:273 # # https://github.com/owncloud/ocis-reva/issues/290 Accessing non-existing public link should return 404, not 500 # -apiSharePublicLink2/uploadToPublicLinkShare.feature:62 -apiSharePublicLink2/uploadToPublicLinkShare.feature:63 apiSharePublicLink2/uploadToPublicLinkShare.feature:66 # # https://github.com/owncloud/ocis-reva/issues/195 Set quota over settings @@ -1763,8 +1756,6 @@ apiShareManagementToShares/moveReceivedShare.feature:70 apiShareManagementToShares/moveReceivedShare.feature:71 apiShareManagementToShares/moveReceivedShare.feature:73 apiShareManagementToShares/moveReceivedShare.feature:88 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:38 -apiShareOperationsToShares/getWebDAVSharePermissions.feature:39 apiShareOperationsToShares/getWebDAVSharePermissions.feature:59 apiShareOperationsToShares/getWebDAVSharePermissions.feature:60 apiShareOperationsToShares/getWebDAVSharePermissions.feature:73 From 7f2ca3aa93dd09301e4b6742ef17ca4a278494d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Tue, 5 Jan 2021 11:11:03 +0100 Subject: [PATCH 33/41] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Phil Davis Signed-off-by: Jörn Friedrich Dreyer --- internal/http/services/owncloud/ocs/conversions/role.go | 2 +- pkg/storage/fs/ocis/node.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/http/services/owncloud/ocs/conversions/role.go b/internal/http/services/owncloud/ocs/conversions/role.go index 87f4aff74b..3a64b921e0 100644 --- a/internal/http/services/owncloud/ocs/conversions/role.go +++ b/internal/http/services/owncloud/ocs/conversions/role.go @@ -298,7 +298,7 @@ func RoleFromOCSPermissions(p Permissions) *Role { return NewLegacyRoleFromOCSPermissions(p) } -// NewLegacyRoleFromOCSPermissions tries to map ocs a lagecy combination of ocs permissions to cs3 resource permissions as a legacy role +// NewLegacyRoleFromOCSPermissions tries to map a legacy combination of ocs permissions to cs3 resource permissions as a legacy role func NewLegacyRoleFromOCSPermissions(p Permissions) *Role { r := &Role{ Name: RoleLegacy, // TODO custom role? diff --git a/pkg/storage/fs/ocis/node.go b/pkg/storage/fs/ocis/node.go index 6f275c69a2..851597f12e 100644 --- a/pkg/storage/fs/ocis/node.go +++ b/pkg/storage/fs/ocis/node.go @@ -311,7 +311,7 @@ func (n *Node) Owner() (o *userpb.UserId, err error) { } // PermissionSet returns the permission set for the current user -// the parent nodes are not takeen into account +// the parent nodes are not taken into account func (n *Node) PermissionSet(ctx context.Context) *provider.ResourcePermissions { u, ok := user.ContextGetUser(ctx) if !ok { @@ -521,7 +521,7 @@ func (n *Node) ReadUserPermissions(ctx context.Context, u *userpb.User) (ap *pro // we have two options here: // 1. we can start iterating over the acls / grants on the node or // 2. we can iterate over the number of groups - // The current implementation tries to be defensive for cases where users have hundreds or thousants of groups, so we iterate over the existing acls. + // The current implementation tries to be defensive for cases where users have hundreds or thousands of groups, so we iterate over the existing acls. userace := grantPrefix + _userAcePrefix + u.Id.OpaqueId userFound := false for i := range grantees { From 605fb1f9be9f0ba051fb609657ecfe6172ae6797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Tue, 5 Jan 2021 14:40:39 +0000 Subject: [PATCH 34/41] clarify ocPublicPermToRole MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../owncloud/ocs/handlers/apps/sharing/shares/public.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go index 31bd739e17..430996df8b 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go @@ -449,10 +449,12 @@ func (h *Handler) removePublicShare(w http.ResponseWriter, r *http.Request, shar } func ocPublicPermToCs3(permKey int, h *Handler) (*provider.ResourcePermissions, error) { - role, ok := ocPublicPermToRole[permKey] + // TODO refactor this ocPublicPermToRole[permKey] check into a conversions.NewPublicSharePermissions? + // not all permissions are possible for public shares + _, ok := ocPublicPermToRole[permKey] if !ok { - log.Error().Str("ocPublicPermToCs3", "shares").Msgf("invalid oC permission: %s", role) - return nil, fmt.Errorf("invalid oC permission: %s", role) + log.Error().Str("ocPublicPermToCs3", "shares").Int("perm", permKey).Msg("invalid public share permission") + return nil, fmt.Errorf("invalid public share permission: %d", permKey) } perm, err := conversions.NewPermissions(permKey) From 92db0fdd500ff5c4f5f697f3015b1a4b9d89f482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 6 Jan 2021 14:36:50 +0000 Subject: [PATCH 35/41] update expected test failures and changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../share-permissions-and-resource-permissions.md | 8 ++++++-- .../acceptance/expected-failures-on-OWNCLOUD-storage.txt | 9 +-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/changelog/unreleased/share-permissions-and-resource-permissions.md b/changelog/unreleased/share-permissions-and-resource-permissions.md index e3b3abcf26..adf57e573c 100644 --- a/changelog/unreleased/share-permissions-and-resource-permissions.md +++ b/changelog/unreleased/share-permissions-and-resource-permissions.md @@ -4,6 +4,10 @@ Instead of hardcoding the permissions set for every file and folder to ListConta https://github.com/cs3org/reva/pull/1368 https://github.com/owncloud/ocis/issues/552 +https://github.com/owncloud/ocis/issues/763 https://github.com/owncloud/ocis/issues/893 -https://github.com/owncloud/product/issues/270 -https://github.com/owncloud/ocis-reva/issues/47 \ No newline at end of file +https://github.com/owncloud/ocis/issues/1126 +https://github.com/owncloud/ocis-reva/issues/47 +https://github.com/owncloud/ocis-reva/issues/315 +https://github.com/owncloud/ocis-reva/issues/316 +https://github.com/owncloud/product/issues/270 \ No newline at end of file diff --git a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt index e5591349a8..8167653edd 100644 --- a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt +++ b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt @@ -2200,13 +2200,6 @@ apiWebdavUploadTUS/uploadFile.feature:156 apiShareManagementBasicToShares/createShareToSharesFolder.feature:632 apiShareManagementBasicToShares/createShareToSharesFolder.feature:633 -# https://github.com/owncloud/ocis/issues/1126 share owner cannot delete other user's files -apiShareUpdateToShares/updateShare.feature:384 -apiShareUpdateToShares/updateShare.feature:385 - # https://github.com/owncloud/ocis/issues/541 Deletion time in trash bin shows a wrong date apiTrashbin/trashbinFilesFolders.feature:284 -apiTrashbin/trashbinFilesFolders.feature:285 - -# https://github.com/owncloud/ocis/issues/763 reading a file that a collaborator uploaded is impossible -apiShareOperationsToShares/uploadToShare.feature:280 +apiTrashbin/trashbinFilesFolders.feature:285 \ No newline at end of file From e36dd46b8d30a8c6a647d2ffd05948c835107653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 7 Jan 2021 15:16:07 +0000 Subject: [PATCH 36/41] parent owner becomes owner of uploaded files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- pkg/storage/fs/ocis/upload.go | 45 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/pkg/storage/fs/ocis/upload.go b/pkg/storage/fs/ocis/upload.go index e4d4072410..f531275cdf 100644 --- a/pkg/storage/fs/ocis/upload.go +++ b/pkg/storage/fs/ocis/upload.go @@ -182,6 +182,12 @@ func (fs *ocisfs) NewUpload(ctx context.Context, info tusd.FileInfo) (upload tus log.Debug().Interface("info", info).Interface("node", n).Msg("ocisfs: resolved filename") + // the parent owner will become the new owner + p, perr := n.Parent() + if perr != nil { + return nil, errors.Wrap(perr, "ocisfs: error getting parent "+n.ParentID) + } + // check permissions var ok bool if n.Exists { @@ -191,11 +197,6 @@ func (fs *ocisfs) NewUpload(ctx context.Context, info tusd.FileInfo) (upload tus }) } else { // check permissions of parent - p, perr := n.Parent() - if perr != nil { - return nil, errors.Wrap(perr, "ocisfs: error getting parent "+n.ParentID) - } - ok, err = fs.p.HasPermission(ctx, p, func(rp *provider.ResourcePermissions) bool { return rp.InitiateFileUpload }) @@ -214,6 +215,12 @@ func (fs *ocisfs) NewUpload(ctx context.Context, info tusd.FileInfo) (upload tus return nil, errors.Wrap(err, "ocisfs: error resolving upload path") } usr := user.ContextMustGetUser(ctx) + + owner, err := p.Owner() + if err != nil { + return nil, errors.Wrap(err, "ocisfs: error determining owner") + } + info.Storage = map[string]string{ "Type": "OCISStore", "BinPath": binPath, @@ -226,6 +233,9 @@ func (fs *ocisfs) NewUpload(ctx context.Context, info tusd.FileInfo) (upload tus "UserId": usr.Id.OpaqueId, "UserName": usr.Username, + "OwnerIdp": owner.Idp, + "OwnerId": owner.OpaqueId, + "LogLevel": log.GetLevel().String(), } // Create binary file in the upload folder with no content @@ -417,25 +427,14 @@ func (upload *fileUpload) FinishUpload(ctx context.Context) (err error) { Msg("ocisfs: could not rename") return } - // who will become the owner? - u, ok := user.ContextGetUser(upload.ctx) - switch { - case ok: - err = n.writeMetadata(u.Id) - case upload.fs.o.EnableHome: - log := appctx.GetLogger(upload.ctx) - log.Error().Msg("home support enabled but no user in context") - err = errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from upload ctx") - case upload.fs.o.Owner != "": - err = n.writeMetadata(&userpb.UserId{ - OpaqueId: upload.fs.o.Owner, - }) - default: - // fallback to parent owner? - err = n.writeMetadata(nil) - } + + // who will become the owner? the owner of the parent actually ... not the currently logged in user + err = n.writeMetadata(&userpb.UserId{ + Idp: upload.info.Storage["OwnerIdp"], + OpaqueId: upload.info.Storage["OwnerId"], + }) if err != nil { - return + return errors.Wrap(err, "ocisfs: could not write metadata") } // link child name to parent if it is new From 0f143624d3ca4f58a9e334122111fb6d9e7bd9d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 7 Jan 2021 15:26:58 +0000 Subject: [PATCH 37/41] parent owner becomes owner of new folders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- pkg/storage/fs/ocis/tree.go | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/pkg/storage/fs/ocis/tree.go b/pkg/storage/fs/ocis/tree.go index 95df12c7a2..7024a6448f 100644 --- a/pkg/storage/fs/ocis/tree.go +++ b/pkg/storage/fs/ocis/tree.go @@ -28,7 +28,6 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" - "github.com/cs3org/reva/pkg/user" "github.com/google/uuid" "github.com/pkg/errors" "github.com/pkg/xattr" @@ -94,27 +93,19 @@ func (t *Tree) CreateDir(ctx context.Context, node *Node) (err error) { // create a directory node node.ID = uuid.New().String() - // who will become the owner? - u, ok := user.ContextGetUser(ctx) - switch { - case ok: - // we have a user in context - err = createNode(node, u.Id) - case t.lu.Options.EnableHome: - // enable home requires a user - log := appctx.GetLogger(ctx) - log.Error().Msg("home support enabled but no user in context") - err = errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") - case t.lu.Options.Owner != "": - // fallback to owner? - err = createNode(node, &userpb.UserId{ - OpaqueId: t.lu.Options.Owner, - }) - default: - // fallback to parent owner? - err = createNode(node, nil) + // who will become the owner? the owner of the parent node, not the current user + var p *Node + p, err = node.Parent() + if err != nil { + return + } + var owner *userpb.UserId + owner, err = p.Owner() + if err != nil { + return } + err = createNode(node, owner) if err != nil { return nil } From 074d616d3eec8e10851a274fc88330fedcf92eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 7 Jan 2021 15:34:16 +0000 Subject: [PATCH 38/41] fix home creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- pkg/storage/fs/ocis/ocis.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/storage/fs/ocis/ocis.go b/pkg/storage/fs/ocis/ocis.go index 98deac1e8a..b9f0a1709b 100644 --- a/pkg/storage/fs/ocis/ocis.go +++ b/pkg/storage/fs/ocis/ocis.go @@ -205,6 +205,12 @@ func (fs *ocisfs) CreateHome(ctx context.Context) (err error) { return nil }) + // update the owner + u := user.ContextMustGetUser(ctx) + if err = h.writeMetadata(u.Id); err != nil { + return + } + if fs.o.TreeTimeAccounting { homePath := h.lu.toInternalPath(h.ID) // mark the home node as the end of propagation From 79f82c6b77fca9ef55fde39a7ce98fbb2e01a5b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 7 Jan 2021 16:02:39 +0000 Subject: [PATCH 39/41] update expected failures and changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- ...re-permissions-and-resource-permissions.md | 1 + .../expected-failures-on-OCIS-storage.txt | 37 ------------------- .../expected-failures-on-OWNCLOUD-storage.txt | 4 -- 3 files changed, 1 insertion(+), 41 deletions(-) diff --git a/changelog/unreleased/share-permissions-and-resource-permissions.md b/changelog/unreleased/share-permissions-and-resource-permissions.md index adf57e573c..e53911dd45 100644 --- a/changelog/unreleased/share-permissions-and-resource-permissions.md +++ b/changelog/unreleased/share-permissions-and-resource-permissions.md @@ -4,6 +4,7 @@ Instead of hardcoding the permissions set for every file and folder to ListConta https://github.com/cs3org/reva/pull/1368 https://github.com/owncloud/ocis/issues/552 +https://github.com/owncloud/ocis/issues/762 https://github.com/owncloud/ocis/issues/763 https://github.com/owncloud/ocis/issues/893 https://github.com/owncloud/ocis/issues/1126 diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.txt b/tests/acceptance/expected-failures-on-OCIS-storage.txt index 795b2ab7fe..ff1fc7577e 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.txt +++ b/tests/acceptance/expected-failures-on-OCIS-storage.txt @@ -428,12 +428,8 @@ apiSharePublicLink1/createPublicLinkShare.feature:371 # # https://github.com/owncloud/ocis-reva/issues/12 Range Header is not obeyed when downloading a file # -apiSharePublicLink1/createPublicLinkShare.feature:63 -apiSharePublicLink1/createPublicLinkShare.feature:64 apiSharePublicLink1/createPublicLinkShare.feature:95 apiSharePublicLink1/createPublicLinkShare.feature:96 -apiSharePublicLink1/createPublicLinkShare.feature:245 -apiSharePublicLink1/createPublicLinkShare.feature:246 apiSharePublicLink1/createPublicLinkShare.feature:276 apiSharePublicLink1/createPublicLinkShare.feature:277 # @@ -1313,11 +1309,6 @@ apiWebdavEtagPropagation2/restoreFromTrash.feature:91 # apiWebdavEtagPropagation2/restoreVersion.feature:10 # -# https://github.com/owncloud/ocis/issues/762 path and other information are not shown if a share does not have "read" permission -# -apiShareOperationsToShares/uploadToShare.feature:64 -apiShareOperationsToShares/uploadToShare.feature:65 -# # https://github.com/owncloud/product/issues/293 sharing with group not available # apiShareOperationsToShares/uploadToShare.feature:39 @@ -1327,11 +1318,6 @@ apiShareOperationsToShares/uploadToShare.feature:92 apiShareOperationsToShares/uploadToShare.feature:139 apiShareOperationsToShares/uploadToShare.feature:140 # -# https://github.com/owncloud/ocis/issues/763 [OCIS-storage] reading a file that a collaborator uploaded is impossible -# -apiShareOperationsToShares/uploadToShare.feature:114 -apiShareOperationsToShares/uploadToShare.feature:115 -# # https://github.com/owncloud/product/issues/247 changing user quota gives ocs status 103 / Cannot set quota # apiShareOperationsToShares/uploadToShare.feature:162 @@ -1367,18 +1353,11 @@ apiShareOperationsToShares/changingFilesShare.feature:60 # [OCIS-storage] overwriting a file as share receiver, does not create a new file version for the sharer https://github.com/owncloud/ocis/issues/766 # apiVersions/fileVersionsSharingToShares.feature:33 -apiVersions/fileVersionsSharingToShares.feature:56 # # restoring an older version of a shared file deletes the share https://github.com/owncloud/ocis/issues/765 # apiVersions/fileVersionsSharingToShares.feature:44 # -# [OCIS-storage] reading a file that a collaborator uploaded is impossible https://github.com/owncloud/ocis/issues/763 -# -apiVersions/fileVersionsSharingToShares.feature:82 -apiVersions/fileVersionsSharingToShares.feature:95 -apiVersions/fileVersionsSharingToShares.feature:108 -# # https://github.com/owncloud/ocis/issues/560 cannot move from Shares folder # apiVersions/fileVersionsSharingToShares.feature:134 @@ -2138,14 +2117,6 @@ apiWebdavUploadTUS/uploadFileMtimeShares.feature:56 apiWebdavUploadTUS/uploadFileMtimeShares.feature:70 apiWebdavUploadTUS/uploadFileMtimeShares.feature:71 -# https://github.com/owncloud/ocis/issues/968 [ocis-storage] PROPFIND on a file uploaded by share receiver is not possible -apiWebdavUploadTUS/uploadToShare.feature:23 -apiWebdavUploadTUS/uploadToShare.feature:24 -apiWebdavUploadTUS/uploadToShare.feature:37 -apiWebdavUploadTUS/uploadToShare.feature:38 -apiWebdavUploadTUS/uploadToShare.feature:66 -apiWebdavUploadTUS/uploadToShare.feature:67 - # https://github.com/owncloud/product/issues/293 sharing with group not available apiWebdavUploadTUS/uploadToShare.feature:52 apiWebdavUploadTUS/uploadToShare.feature:53 @@ -2168,17 +2139,9 @@ apiWebdavUploadTUS/uploadFile.feature:156 apiShareManagementBasicToShares/createShareToSharesFolder.feature:632 apiShareManagementBasicToShares/createShareToSharesFolder.feature:633 -# https://github.com/owncloud/ocis/issues/1126 share owner cannot delete other user's files -apiShareUpdateToShares/updateShare.feature:384 -apiShareUpdateToShares/updateShare.feature:385 - # https://github.com/owncloud/ocis/issues/541 Deletion time in trash bin shows a wrong date apiTrashbin/trashbinFilesFolders.feature:284 apiTrashbin/trashbinFilesFolders.feature:285 -# https://github.com/owncloud/ocis/issues/763 reading a file that a collaborator uploaded is impossible -apiShareOperationsToShares/uploadToShare.feature:279 -apiShareOperationsToShares/uploadToShare.feature:280 - # https://github.com/owncloud/ocis/issues/766 [OCIS-storage] overwriting a file as share receiver, does not create a new file version for the sharer apiVersions/fileVersionsSharingToShares.feature:291 diff --git a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt index 8167653edd..3ec928c247 100644 --- a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt +++ b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.txt @@ -400,12 +400,8 @@ apiSharePublicLink1/createPublicLinkShare.feature:371 # # https://github.com/owncloud/ocis-reva/issues/12 Range Header is not obeyed when downloading a file # -apiSharePublicLink1/createPublicLinkShare.feature:63 -apiSharePublicLink1/createPublicLinkShare.feature:64 apiSharePublicLink1/createPublicLinkShare.feature:95 apiSharePublicLink1/createPublicLinkShare.feature:96 -apiSharePublicLink1/createPublicLinkShare.feature:245 -apiSharePublicLink1/createPublicLinkShare.feature:246 apiSharePublicLink1/createPublicLinkShare.feature:276 apiSharePublicLink1/createPublicLinkShare.feature:277 # From c69f4705e19aa25c0e4488f63c05a8e2bfda1a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 7 Jan 2021 16:08:46 +0000 Subject: [PATCH 40/41] fix lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- pkg/storage/fs/ocis/ocis.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/storage/fs/ocis/ocis.go b/pkg/storage/fs/ocis/ocis.go index b9f0a1709b..1c3e211511 100644 --- a/pkg/storage/fs/ocis/ocis.go +++ b/pkg/storage/fs/ocis/ocis.go @@ -204,6 +204,9 @@ func (fs *ocisfs) CreateHome(ctx context.Context) (err error) { } return nil }) + if err != nil { + return + } // update the owner u := user.ContextMustGetUser(ctx) From c3cbdd19562f63eaf118b0ae4819a581acfcabd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 8 Jan 2021 14:50:33 +0000 Subject: [PATCH 41/41] cleanup permissions rendering for propfind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../http/services/owncloud/ocdav/propfind.go | 73 ++++--------------- 1 file changed, 15 insertions(+), 58 deletions(-) diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 0ee3b5fcc9..b0c17af211 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -280,15 +280,6 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide Propstat: []propstatXML{}, } - role := conversions.RoleFromResourcePermissions(md.PermissionSet) - - // files never have the create container permission - if md.Type == provider.ResourceType_RESOURCE_TYPE_FILE && md.PermissionSet != nil { - // we need to copy the permission set in case it was reused when passing it in while listing a collection - md.PermissionSet = copyPermissionSet(md.PermissionSet) - md.PermissionSet.CreateContainer = false - } - var ls *link.PublicShare if md.Opaque != nil && md.Opaque.Map != nil && md.Opaque.Map["link-share"] != nil && md.Opaque.Map["link-share"].Decoder == "json" { ls = &link.PublicShare{} @@ -298,7 +289,19 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } } + role := conversions.RoleFromResourcePermissions(md.PermissionSet) + isShared := !isCurrentUserOwner(ctx, md.Owner) + var wdp string + if md.PermissionSet != nil { + wdp = role.WebDAVPermissions( + md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, + isShared, + false, + false, + ) + sublog.Debug().Interface("role", role).Str("dav-permissions", wdp).Msg("converted PermissionSet") + } // when allprops has been requested if pf.Allprop != nil { @@ -324,16 +327,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } if md.PermissionSet != nil { - wdp := role.WebDAVPermissions( - md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, - isShared, - false, - false, - ) - sublog.Debug().Interface("role", role).Str("dav-permissions", wdp).Msg("converted PermissionSet") - response.Propstat[0].Prop = append( - response.Propstat[0].Prop, - s.newProp("oc:permissions", wdp)) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:permissions", wdp)) } // always return size @@ -420,25 +414,15 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // M = Mounted // in contrast, the ocs:share-permissions further down below indicate clients the maximum permissions that can be granted if md.PermissionSet != nil { - wdp := role.WebDAVPermissions( - md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, - isShared, - false, - false, - ) - sublog.Debug().Interface("role", role).Str("dav-permissions", wdp).Msg("converted PermissionSet") - propstatOK.Prop = append( - propstatOK.Prop, - s.newProp("oc:permissions", wdp)) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:permissions", wdp)) } else { propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:permissions", "")) } case "public-link-permission": // only on a share root node if ls != nil && md.PermissionSet != nil { - r := conversions.RoleFromResourcePermissions(md.PermissionSet) propstatOK.Prop = append( propstatOK.Prop, - s.newProp("oc:public-link-permission", strconv.FormatUint(uint64(r.OCSPermissions()), 10))) + s.newProp("oc:public-link-permission", strconv.FormatUint(uint64(role.OCSPermissions()), 10))) } else { propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-permission", "")) } @@ -648,33 +632,6 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide return &response, nil } -// the permission set that is passed in needs to be copied so we don't accidentially overwrite the permission set of other entries -func copyPermissionSet(p *provider.ResourcePermissions) *provider.ResourcePermissions { - if p == nil { - return nil - } - return &provider.ResourcePermissions{ - AddGrant: p.AddGrant, - CreateContainer: p.CreateContainer, - Delete: p.Delete, - GetPath: p.GetPath, - GetQuota: p.GetQuota, - InitiateFileDownload: p.InitiateFileDownload, - InitiateFileUpload: p.InitiateFileUpload, - ListGrants: p.ListGrants, - ListContainer: p.ListContainer, - ListFileVersions: p.ListFileVersions, - ListRecycle: p.ListRecycle, - Move: p.Move, - RemoveGrant: p.RemoveGrant, - PurgeRecycle: p.PurgeRecycle, - RestoreFileVersion: p.RestoreFileVersion, - RestoreRecycleItem: p.RestoreRecycleItem, - Stat: p.Stat, - UpdateGrant: p.UpdateGrant, - } -} - // a file is only yours if you are the owner func isCurrentUserOwner(ctx context.Context, owner *userv1beta1.UserId) bool { contextUser, ok := ctxuser.ContextGetUser(ctx)