Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make user share indicators read from the share provider service #2946

Merged
merged 2 commits into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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