diff --git a/changelog/unreleased/user-share-indicators.md b/changelog/unreleased/user-share-indicators.md new file mode 100644 index 0000000000..949906deae --- /dev/null +++ b/changelog/unreleased/user-share-indicators.md @@ -0,0 +1,3 @@ +Enhancement: Make user share indicators read from the share provider service + +https://github.com/cs3org/reva/pull/2946 \ No newline at end of file diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 7fd60c19cc..0fce4905ac 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -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" @@ -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" @@ -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() @@ -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) @@ -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 } @@ -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) @@ -804,6 +820,10 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide types.WriteString("") types.WriteString(amdv) types.WriteString("") + } else if md.Id != nil { + if _, ok := usershares[resourceid.OwnCloudResourceIDWrap(md.Id)]; ok { + types.WriteString("0") + } } if md.Id != nil { diff --git a/internal/http/services/owncloud/ocdav/publicfile.go b/internal/http/services/owncloud/ocdav/publicfile.go index b7a62d8bc7..d5b6c54c9a 100644 --- a/internal/http/services/owncloud/ocdav/publicfile.go +++ b/internal/http/services/owncloud/ocdav/publicfile.go @@ -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) diff --git a/internal/http/services/owncloud/ocdav/report.go b/internal/http/services/owncloud/ocdav/report.go index 39729be770..da921905af 100644 --- a/internal/http/services/owncloud/ocdav/report.go +++ b/internal/http/services/owncloud/ocdav/report.go @@ -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) diff --git a/internal/http/services/owncloud/ocdav/versions.go b/internal/http/services/owncloud/ocdav/versions.go index 75467dd39e..b1f3db1eae 100644 --- a/internal/http/services/owncloud/ocdav/versions.go +++ b/internal/http/services/owncloud/ocdav/versions.go @@ -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) diff --git a/pkg/cbox/publicshare/sql/sql.go b/pkg/cbox/publicshare/sql/sql.go index b086426dfe..d3d94f8516 100644 --- a/pkg/cbox/publicshare/sql/sql.go +++ b/pkg/cbox/publicshare/sql/sql.go @@ -32,6 +32,7 @@ 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" @@ -39,12 +40,20 @@ import ( "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) @@ -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 { @@ -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() { @@ -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...) @@ -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 diff --git a/pkg/cbox/share/sql/sql.go b/pkg/cbox/share/sql/sql.go index c7bd3caa69..c3ecd8e730 100644 --- a/pkg/cbox/share/sql/sql.go +++ b/pkg/cbox/share/sql/sql.go @@ -27,14 +27,17 @@ import ( "strings" "time" + 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" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" conversions "github.com/cs3org/reva/pkg/cbox/utils" ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/share" "github.com/cs3org/reva/pkg/share/manager/registry" + "github.com/cs3org/reva/pkg/sharedconf" "github.com/cs3org/reva/pkg/utils" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" @@ -47,6 +50,10 @@ import ( const ( shareTypeUser = 0 shareTypeGroup = 1 + + projectInstancesPrefix = "newproject" + projectSpaceGroupsPrefix = "cernbox-project-" + projectSpaceAdminGroupsSuffix = "-admins" ) func init() { @@ -59,6 +66,7 @@ type config struct { DbHost string `mapstructure:"db_host"` DbPort int `mapstructure:"db_port"` DbName string `mapstructure:"db_name"` + GatewaySvc string `mapstructure:"gatewaysvc"` } type mgr struct { @@ -90,6 +98,7 @@ func parseConfig(m map[string]interface{}) (*config, error) { if err := mapstructure.Decode(m, c); err != nil { return nil, err } + c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) return c, nil } @@ -281,20 +290,15 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference } func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { - uid := conversions.FormatUserID(ctxpkg.ContextMustGetUser(ctx).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, id, stime, permissions, share_type - FROM oc_share - WHERE (orphan = 0 or orphan IS NULL) AND (uid_owner=? or uid_initiator=?)` - params := []interface{}{uid, uid} + FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND (share_type=? OR share_type=?)` + params := []interface{}{shareTypeUser, shareTypeGroup} - if len(filters) == 0 { - query += " AND (share_type=? OR share_type=?)" - params = append(params, shareTypeUser) - params = append(params, shareTypeGroup) - } else { - filterQuery, filterParams, err := translateFilters(filters) + groupedFilters := share.GroupFiltersByType(filters) + if len(groupedFilters) > 0 { + filterQuery, filterParams, err := translateFilters(groupedFilters) if err != nil { return nil, err } @@ -304,6 +308,15 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ( } } + uidOwnersQuery, uidOwnersParams, err := m.uidOwnerFilters(ctx, groupedFilters) + 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...) if err != nil { return nil, err @@ -346,7 +359,8 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F query += " AND (share_with=? AND share_type = 0)" } - filterQuery, filterParams, err := translateFilters(filters) + groupedFilters := share.GroupFiltersByType(filters) + filterQuery, filterParams, err := translateFilters(groupedFilters) if err != nil { return nil, err } @@ -497,6 +511,58 @@ func (m *mgr) UpdateReceivedShare(ctx context.Context, share *collaboration.Rece return rs, nil } +func (m *mgr) uidOwnerFilters(ctx context.Context, filters map[collaboration.Filter_Type][]*collaboration.Filter) (string, []interface{}, error) { + user := ctxpkg.ContextMustGetUser(ctx) + uid := conversions.FormatUserID(user.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 + } + + if resourceFilters, ok := filters[collaboration.Filter_TYPE_RESOURCE_ID]; ok { + for _, f := range resourceFilters { + // 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 user.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 granteeTypeToShareType(granteeType provider.GranteeType) int { switch granteeType { case provider.GranteeType_GRANTEE_TYPE_USER: @@ -508,38 +574,37 @@ func granteeTypeToShareType(granteeType provider.GranteeType) int { } // translateFilters translates the filters to sql queries -func translateFilters(filters []*collaboration.Filter) (string, []interface{}, error) { +func translateFilters(filters map[collaboration.Filter_Type][]*collaboration.Filter) (string, []interface{}, error) { var ( filterQuery string params []interface{} ) - groupedFilters := share.GroupFiltersByType(filters) // If multiple filters of the same type are passed to this function, they need to be combined with the `OR` operator. // That is why the filters got grouped by type. // For every given filter type, iterate over the filters and if there are more than one combine them. // Combine the different filter types using `AND` var filterCounter = 0 - for filterType, filters := range groupedFilters { + for filterType, currFilters := range filters { switch filterType { case collaboration.Filter_TYPE_RESOURCE_ID: filterQuery += "(" - for i, f := range filters { + for i, f := range currFilters { filterQuery += "(fileid_prefix =? AND item_source=?)" params = append(params, f.GetResourceId().StorageId, f.GetResourceId().OpaqueId) - if i != len(filters)-1 { + if i != len(currFilters)-1 { filterQuery += " OR " } } filterQuery += ")" case collaboration.Filter_TYPE_GRANTEE_TYPE: filterQuery += "(" - for i, f := range filters { + for i, f := range currFilters { filterQuery += "share_type=?" params = append(params, granteeTypeToShareType(f.GetGranteeType())) - if i != len(filters)-1 { + if i != len(currFilters)-1 { filterQuery += " OR " } } @@ -550,7 +615,7 @@ func translateFilters(filters []*collaboration.Filter) (string, []interface{}, e default: return "", nil, fmt.Errorf("filter type is not supported") } - if filterCounter != len(groupedFilters)-1 { + if filterCounter != len(filters)-1 { filterQuery += " AND " } filterCounter++