Skip to content

Commit

Permalink
Make user share indicators read from the share provider service (#2946)
Browse files Browse the repository at this point in the history
  • Loading branch information
ishank011 authored Jun 9, 2022
1 parent 795861a commit 937e3c8
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 52 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/user-share-indicators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: Make user share indicators read from the share provider service

https://github.com/cs3org/reva/pull/2946
38 changes: 29 additions & 9 deletions internal/http/services/owncloud/ocdav/propfind.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (

userv1beta1 "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"
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/grpc/services/storageprovider"
Expand All @@ -43,6 +44,7 @@ import (
ctxpkg "github.com/cs3org/reva/pkg/ctx"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/publicshare"
"github.com/cs3org/reva/pkg/share"
rtrace "github.com/cs3org/reva/pkg/trace"
"github.com/cs3org/reva/pkg/utils"
"github.com/cs3org/reva/pkg/utils/resourceid"
Expand Down Expand Up @@ -146,9 +148,11 @@ func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *ht
ctx, span := rtrace.Provider.Tracer("ocdav").Start(ctx, "propfind_response")
defer span.End()

filters := make([]*link.ListPublicSharesRequest_Filter, 0, len(resourceInfos))
linkFilters := make([]*link.ListPublicSharesRequest_Filter, 0, len(resourceInfos))
shareFilters := make([]*collaboration.Filter, 0, len(resourceInfos))
for i := range resourceInfos {
filters = append(filters, publicshare.ResourceIDFilter(resourceInfos[i].Id))
linkFilters = append(linkFilters, publicshare.ResourceIDFilter(resourceInfos[i].Id))
shareFilters = append(shareFilters, share.ResourceIDFilter(resourceInfos[i].Id))
}

client, err := s.getClient()
Expand All @@ -159,18 +163,30 @@ func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *ht
}

var linkshares map[string]struct{}
listResp, err := client.ListPublicShares(ctx, &link.ListPublicSharesRequest{Filters: filters})
listResp, err := client.ListPublicShares(ctx, &link.ListPublicSharesRequest{Filters: linkFilters})
if err == nil {
linkshares = make(map[string]struct{}, len(listResp.Share))
for i := range listResp.Share {
linkshares[listResp.Share[i].ResourceId.OpaqueId] = struct{}{}
linkshares[resourceid.OwnCloudResourceIDWrap(listResp.Share[i].ResourceId)] = struct{}{}
}
} else {
log.Error().Err(err).Msg("propfindResponse: couldn't list public shares")
span.SetStatus(codes.Error, err.Error())
}

propRes, err := s.multistatusResponse(ctx, &pf, resourceInfos, namespace, linkshares)
var usershares map[string]struct{}
listSharesResp, err := client.ListShares(ctx, &collaboration.ListSharesRequest{Filters: shareFilters})
if err == nil {
usershares = make(map[string]struct{}, len(listSharesResp.Shares))
for i := range listSharesResp.Shares {
usershares[resourceid.OwnCloudResourceIDWrap(listSharesResp.Shares[i].ResourceId)] = struct{}{}
}
} else {
log.Error().Err(err).Msg("propfindResponse: couldn't list user shares")
span.SetStatus(codes.Error, err.Error())
}

propRes, err := s.multistatusResponse(ctx, &pf, resourceInfos, namespace, usershares, linkshares)
if err != nil {
log.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
Expand Down Expand Up @@ -431,10 +447,10 @@ func readPropfind(r io.Reader) (pf propfindXML, status int, err error) {
return pf, 0, nil
}

func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string, linkshares map[string]struct{}) (string, error) {
func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string, usershares, linkshares map[string]struct{}) (string, error) {
responses := make([]*responseXML, 0, len(mds))
for i := range mds {
res, err := s.mdToPropResponse(ctx, pf, mds[i], ns, linkshares)
res, err := s.mdToPropResponse(ctx, pf, mds[i], ns, usershares, linkshares)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -486,8 +502,8 @@ func (s *svc) newPropRaw(key, val string) *propertyXML {
// mdToPropResponse converts the CS3 metadata into a webdav PropResponse
// 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, linkshares map[string]struct{}) (*responseXML, error) {
sublog := appctx.GetLogger(ctx).With().Interface("md", md).Str("ns", ns).Logger()
func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provider.ResourceInfo, ns string, usershares, linkshares map[string]struct{}) (*responseXML, error) {
sublog := appctx.GetLogger(ctx).With().Str("ns", ns).Logger()
md.Path = strings.TrimPrefix(md.Path, ns)

baseURI := ctx.Value(ctxKeyBaseURI).(string)
Expand Down Expand Up @@ -804,6 +820,10 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide
types.WriteString("<oc:share-type>")
types.WriteString(amdv)
types.WriteString("</oc:share-type>")
} else if md.Id != nil {
if _, ok := usershares[resourceid.OwnCloudResourceIDWrap(md.Id)]; ok {
types.WriteString("<oc:share-type>0</oc:share-type>")
}
}

if md.Id != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/http/services/owncloud/ocdav/publicfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s
}
infos := s.getPublicFileInfos(onContainer, depth == "0", tokenStatInfo)

propRes, err := s.multistatusResponse(ctx, &pf, infos, ns, nil)
propRes, err := s.multistatusResponse(ctx, &pf, infos, ns, nil, nil)
if err != nil {
sublog.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
Expand Down
2 changes: 1 addition & 1 deletion internal/http/services/owncloud/ocdav/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFi
infos = append(infos, statRes.Info)
}

responsesXML, err := s.multistatusResponse(ctx, &propfindXML{Prop: ff.Prop}, infos, namespace, nil)
responsesXML, err := s.multistatusResponse(ctx, &propfindXML{Prop: ff.Prop}, infos, namespace, nil, nil)
if err != nil {
log.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
Expand Down
2 changes: 1 addition & 1 deletion internal/http/services/owncloud/ocdav/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request,
infos = append(infos, vi)
}

propRes, err := s.multistatusResponse(ctx, &pf, infos, "", nil)
propRes, err := s.multistatusResponse(ctx, &pf, infos, "", nil, nil)
if err != nil {
sublog.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
Expand Down
123 changes: 102 additions & 21 deletions pkg/cbox/publicshare/sql/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,28 @@ import (
"golang.org/x/crypto/bcrypt"

user "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"
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
conversions "github.com/cs3org/reva/pkg/cbox/utils"
"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/todo/pool"
"github.com/cs3org/reva/pkg/sharedconf"
"github.com/cs3org/reva/pkg/utils"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)

const publicShareType = 3
const (
publicShareType = 3

projectInstancesPrefix = "newproject"
projectSpaceGroupsPrefix = "cernbox-project-"
projectSpaceAdminGroupsSuffix = "-admins"
)

func init() {
registry.Register("sql", New)
Expand All @@ -59,6 +68,7 @@ type config struct {
DbHost string `mapstructure:"db_host"`
DbPort int `mapstructure:"db_port"`
DbName string `mapstructure:"db_name"`
GatewaySvc string `mapstructure:"gatewaysvc"`
}

type manager struct {
Expand All @@ -73,6 +83,8 @@ func (c *config) init() {
if c.JanitorRunInterval == 0 {
c.JanitorRunInterval = 3600
}

c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc)
}

func (m *manager) startJanitorRun() {
Expand Down Expand Up @@ -309,35 +321,53 @@ func (m *manager) GetPublicShare(ctx context.Context, u *user.User, ref *link.Pu
}

func (m *manager) ListPublicShares(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter, md *provider.ResourceInfo, sign bool) ([]*link.PublicShare, error) {
uid := conversions.FormatUserID(u.Id)
query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, coalesce(item_type, '') as item_type, coalesce(token,'') as token, coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, id, stime, permissions FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND (uid_owner=? or uid_initiator=?) AND (share_type=?)"
var filterQuery string
params := []interface{}{uid, uid, publicShareType}

for i, f := range filters {
query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, coalesce(item_type, '') as item_type, coalesce(token,'') as token, coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, id, stime, permissions FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND (share_type=?)"
var resourceFilters, ownerFilters, creatorFilters string
var resourceParams, ownerParams, creatorParams []interface{}
params := []interface{}{publicShareType}
for _, f := range filters {
switch f.Type {
case link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID:
filterQuery += "(fileid_prefix=? AND item_source=?)"
if i != len(filters)-1 {
filterQuery += " AND "
if len(resourceFilters) != 0 {
resourceFilters += " OR "
}
params = append(params, f.GetResourceId().StorageId, f.GetResourceId().OpaqueId)
resourceFilters += "(fileid_prefix=? AND item_source=?)"
resourceParams = append(resourceParams, f.GetResourceId().StorageId, f.GetResourceId().OpaqueId)
case link.ListPublicSharesRequest_Filter_TYPE_OWNER:
filterQuery += "(uid_owner=?)"
if i != len(filters)-1 {
filterQuery += " AND "
if len(ownerFilters) != 0 {
ownerFilters += " OR "
}
params = append(params, conversions.FormatUserID(f.GetOwner()))
ownerFilters += "(uid_owner=?)"
ownerParams = append(ownerParams, conversions.FormatUserID(f.GetOwner()))
case link.ListPublicSharesRequest_Filter_TYPE_CREATOR:
filterQuery += "(uid_initiator=?)"
if i != len(filters)-1 {
filterQuery += " AND "
if len(creatorFilters) != 0 {
creatorFilters += " OR "
}
params = append(params, conversions.FormatUserID(f.GetCreator()))
creatorFilters += "(uid_initiator=?)"
creatorParams = append(creatorParams, conversions.FormatUserID(f.GetCreator()))
}
}
if filterQuery != "" {
query = fmt.Sprintf("%s AND (%s)", query, filterQuery)

if resourceFilters != "" {
query = fmt.Sprintf("%s AND (%s)", query, resourceFilters)
params = append(params, resourceParams...)
}
if ownerFilters != "" {
query = fmt.Sprintf("%s AND (%s)", query, ownerFilters)
params = append(params, ownerParams...)
}
if creatorFilters != "" {
query = fmt.Sprintf("%s AND (%s)", query, creatorFilters)
params = append(params, creatorParams...)
}

uidOwnersQuery, uidOwnersParams, err := m.uidOwnerFilters(ctx, u, filters)
if err != nil {
return nil, err
}
params = append(params, uidOwnersParams...)
if uidOwnersQuery != "" {
query = fmt.Sprintf("%s AND (%s)", query, uidOwnersQuery)
}

rows, err := m.db.Query(query, params...)
Expand Down Expand Up @@ -466,6 +496,57 @@ func expired(s *link.PublicShare) bool {
return false
}

func (m *manager) uidOwnerFilters(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter) (string, []interface{}, error) {
uid := conversions.FormatUserID(u.Id)

query := "uid_owner=? or uid_initiator=?"
params := []interface{}{uid, uid}

client, err := pool.GetGatewayServiceClient(pool.Endpoint(m.c.GatewaySvc))
if err != nil {
return "", nil, err
}

for _, f := range filters {
if f.Type == link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID {
// For shares inside project spaces, if the user is an admin, we try to list all shares created by other admins
if strings.HasPrefix(f.GetResourceId().GetStorageId(), projectInstancesPrefix) {
res, err := client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: f.GetResourceId()}})
if err != nil || res.Status.Code != rpc.Code_CODE_OK {
continue
}

// The path will look like /eos/project/c/cernbox, we need to extract the project name
parts := strings.SplitN(res.Info.Path, "/", 6)
if len(parts) < 5 {
continue
}

adminGroup := projectSpaceGroupsPrefix + parts[4] + projectSpaceAdminGroupsSuffix
for _, g := range u.Groups {
if g == adminGroup {
// User belongs to the admin group, list all shares for the resource

// TODO: this only works if shares for a single project are requested.
// If shares for multiple projects are requested, then we're not checking if the
// user is an admin for all of those. We can append the query ` or uid_owner=?`
// for all the project owners, which works fine for new reva
// but won't work for revaold since there, we store the uid of the share creator as uid_owner.
// For this to work across the two versions, this change would have to be made in revaold
// but it won't be straightforward as there, the storage provider doesn't return the
// resource owners.
query = ""
params = []interface{}{}
break
}
}
}
}
}

return query, params, nil
}

func hashPassword(password string, cost int) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), cost)
return "1|" + string(bytes), err
Expand Down
Loading

0 comments on commit 937e3c8

Please sign in to comment.