diff --git a/changelog/unreleased/ocis-ocs-share-types.md b/changelog/unreleased/ocis-ocs-share-types.md new file mode 100644 index 0000000000..c185458b64 --- /dev/null +++ b/changelog/unreleased/ocis-ocs-share-types.md @@ -0,0 +1,6 @@ +Enhancement: include share types in ocs propfind responses + +Added the share types to the ocs propfind response when a resource has been shared. + +https://github.com/owncloud/ocis/issues/929 +https://github.com/cs3org/reva/pull/1329 diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 22ceb6872a..9d96bb4108 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -1265,13 +1265,14 @@ func (s *svc) ListContainerStream(_ *provider.ListContainerStreamRequest, _ gate return errtypes.NotSupported("Unimplemented") } -func (s *svc) listHome(ctx context.Context) (*provider.ListContainerResponse, error) { +func (s *svc) listHome(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) { lcr, err := s.listContainer(ctx, &provider.ListContainerRequest{ Ref: &provider.Reference{ Spec: &provider.Reference_Path{ Path: s.getHome(ctx), }, }, + ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, }) if err != nil { return &provider.ListContainerResponse{ @@ -1377,7 +1378,7 @@ func (s *svc) ListContainer(ctx context.Context, req *provider.ListContainerRequ } if path.Clean(p) == s.getHome(ctx) { - return s.listHome(ctx) + return s.listHome(ctx, req) } if s.isSharedFolder(ctx, p) { diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 2dc321e79e..d401947a98 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -99,6 +99,9 @@ func (s *svc) handlePropfind(w http.ResponseWriter, r *http.Request, ns string) if info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth == "1" { req := &provider.ListContainerRequest{ Ref: ref, + ArbitraryMetadataKeys: []string{ + "http://owncloud.org/ns/share-types", + }, } res, err := client.ListContainer(ctx, req) if err != nil { @@ -124,6 +127,9 @@ func (s *svc) handlePropfind(w http.ResponseWriter, r *http.Request, ns string) } req := &provider.ListContainerRequest{ Ref: ref, + ArbitraryMetadataKeys: []string{ + "http://owncloud.org/ns/share-types", + }, } res, err := client.ListContainer(ctx, req) if err != nil { @@ -255,7 +261,6 @@ 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) { - md.Path = strings.TrimPrefix(md.Path, ns) baseURI := ctx.Value(ctxKeyBaseURI).(string) @@ -413,6 +418,15 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } else { propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:checksums", "")) } + case "share-types": // desktop + k := md.GetArbitraryMetadata() + amd := k.GetMetadata() + if amdv, ok := amd[fmt.Sprintf("%s/%s", pf.Prop[i].Space, pf.Prop[i].Local)]; ok { + st := fmt.Sprintf("%s", amdv) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:share-types", st)) + } else { + 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 @@ -430,11 +444,6 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // see https://doc.owncloud.com/server/admin_manual/configuration/server/occ_command.html#maintenance-commands // TODO(jfd): double check the client behavior with reva on backup restore fallthrough - case "share-types": // desktop - // - // 1 - // - fallthrough default: propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) } diff --git a/pkg/storage/fs/ocis/grants.go b/pkg/storage/fs/ocis/grants.go index a2a86de354..ae5f3f5364 100644 --- a/pkg/storage/fs/ocis/grants.go +++ b/pkg/storage/fs/ocis/grants.go @@ -124,9 +124,9 @@ func (fs *ocisfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *p var attr string if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - attr = grantPrefix + "g:" + g.Grantee.Id.OpaqueId + attr = grantPrefix + _groupAcePrefix + g.Grantee.Id.OpaqueId } else { - attr = grantPrefix + "u:" + g.Grantee.Id.OpaqueId + attr = grantPrefix + _userAcePrefix + g.Grantee.Id.OpaqueId } np := fs.lu.toInternalPath(node.ID) diff --git a/pkg/storage/fs/ocis/node.go b/pkg/storage/fs/ocis/node.go index bcde7b28cc..d8547aff7f 100644 --- a/pkg/storage/fs/ocis/node.go +++ b/pkg/storage/fs/ocis/node.go @@ -34,12 +34,18 @@ import ( "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/mime" + "github.com/cs3org/reva/pkg/sdk/common" "github.com/cs3org/reva/pkg/storage/utils/ace" "github.com/pkg/errors" "github.com/pkg/xattr" "github.com/rs/zerolog/log" ) +const ( + _shareTypesKey = "http://owncloud.org/ns/share-types" + _userShareType = "0" +) + // Node represents a node in the tree and provides methods to get a Parent or Child instance type Node struct { lu *Lookup @@ -291,7 +297,7 @@ func (n *Node) Owner() (id string, idp string, err error) { } // AsResourceInfo return the node as CS3 ResourceInfo -func (n *Node) AsResourceInfo(ctx context.Context) (ri *provider.ResourceInfo, err error) { +func (n *Node) AsResourceInfo(ctx context.Context, mdKeys []string) (ri *provider.ResourceInfo, err error) { log := appctx.GetLogger(ctx) var fn string @@ -399,6 +405,12 @@ func (n *Node) AsResourceInfo(ctx context.Context) (ri *provider.ResourceInfo, e log.Error().Err(err).Interface("node", n).Msg("could not list attributes") } + if common.FindString(mdKeys, _shareTypesKey) != -1 { + if n.hasUserShares(ctx) { + ri.ArbitraryMetadata.Metadata[_shareTypesKey] = _userShareType + } + } + log.Debug(). Interface("ri", ri). Msg("AsResourceInfo") @@ -469,3 +481,18 @@ func (n *Node) ReadGrant(ctx context.Context, grantee string) (g *provider.Grant } return e.Grant(), nil } + +func (n *Node) hasUserShares(ctx context.Context) bool { + g, err := n.ListGrantees(ctx) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Msg("hasUserShares: listGrantees") + return false + } + + for i := range g { + if strings.Contains(g[i], grantPrefix+_userAcePrefix) { + return true + } + } + return false +} diff --git a/pkg/storage/fs/ocis/ocis.go b/pkg/storage/fs/ocis/ocis.go index 59f092997b..2cb9d2be10 100644 --- a/pkg/storage/fs/ocis/ocis.go +++ b/pkg/storage/fs/ocis/ocis.go @@ -371,7 +371,7 @@ func (fs *ocisfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []s return nil, errtypes.PermissionDenied(node.ID) } - return node.AsResourceInfo(ctx) + return node.AsResourceInfo(ctx, mdKeys) } func (fs *ocisfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) (finfos []*provider.ResourceInfo, err error) { @@ -402,7 +402,7 @@ func (fs *ocisfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKey } for i := range children { - if ri, err := children[i].AsResourceInfo(ctx); err == nil { + if ri, err := children[i].AsResourceInfo(ctx, mdKeys); err == nil { finfos = append(finfos, ri) } } diff --git a/pkg/storage/fs/ocis/permissions.go b/pkg/storage/fs/ocis/permissions.go index 70d7b96dc7..d74c0f4f31 100644 --- a/pkg/storage/fs/ocis/permissions.go +++ b/pkg/storage/fs/ocis/permissions.go @@ -31,6 +31,11 @@ import ( "github.com/pkg/xattr" ) +const ( + _userAcePrefix = "u:" + _groupAcePrefix = "g:" +) + var defaultPermissions *provider.ResourcePermissions = &provider.ResourcePermissions{ // no permissions } @@ -100,15 +105,15 @@ func (p *Permissions) HasPermission(ctx context.Context, n *Node, check func(*pr return false, err } - userace := grantPrefix + "u:" + u.Id.OpaqueId + 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+"g:"): - gr := strings.TrimPrefix(grantees[i], grantPrefix+"g:") + 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 {