From 4ab4bdb01255b709787855bf49b419596c1d3e7e Mon Sep 17 00:00:00 2001 From: David Christofas Date: Tue, 7 Sep 2021 12:19:09 +0200 Subject: [PATCH 1/3] add filter by sharetype in the ocs API --- changelog/unreleased/sharetype-filter.md | 5 + .../handlers/apps/sharing/shares/shares.go | 75 +++++++++-- pkg/share/manager/json/json.go | 68 +++++----- pkg/share/manager/memory/memory.go | 58 ++++----- pkg/share/share.go | 35 ++++++ pkg/share/share_test.go | 119 ++++++++++++++++++ .../expected-failures-on-OCIS-storage.md | 27 ---- .../expected-failures-on-S3NG-storage.md | 27 ---- 8 files changed, 278 insertions(+), 136 deletions(-) create mode 100644 changelog/unreleased/sharetype-filter.md create mode 100644 pkg/share/share_test.go diff --git a/changelog/unreleased/sharetype-filter.md b/changelog/unreleased/sharetype-filter.md new file mode 100644 index 0000000000..fc140d04fa --- /dev/null +++ b/changelog/unreleased/sharetype-filter.md @@ -0,0 +1,5 @@ +Enhancement: Add a share types filter to the OCS API + +Added a filter to the OCS API to filter the received shares by type. + +https://github.com/cs3org/reva/pull/2050 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 304509c868..afc3252811 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 @@ -545,7 +545,36 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { } } - lrsRes, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + filters := []*collaboration.Filter{} + var shareTypes []string + shareTypesParam := r.URL.Query().Get("share_types") + if shareTypesParam != "" { + shareTypes = strings.Split(shareTypesParam, ",") + } + for _, s := range shareTypes { + if s == "" { + continue + } + shareType, err := strconv.Atoi(strings.TrimSpace(s)) + if err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "invalid share type", err) + return + } + switch shareType { + case int(conversions.ShareTypeUser): + filters = append(filters, share.UserGranteeFilter()) + case int(conversions.ShareTypeGroup): + filters = append(filters, share.GroupGranteeFilter()) + } + } + + if len(shareTypes) != 0 && len(filters) == 0 { + // If a share_types filter was set for anything other than user or group shares just return an empty response + response.WriteOCSSuccess(w, r, []*conversions.ShareData{}) + return + } + + lrsRes, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{Filters: filters}) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc ListReceivedShares request", err) return @@ -693,25 +722,47 @@ func (h *Handler) listSharesWithOthers(w http.ResponseWriter, r *http.Request) { } } - shareTypes := strings.Split(r.URL.Query().Get("share_types"), ",") + var shareTypes []string + shareTypesParam := r.URL.Query().Get("share_types") + if shareTypesParam != "" { + shareTypes = strings.Split(shareTypesParam, ",") + } + + listPublicShares := len(shareTypes) == 0 // if no share_types filter was set we want to list all share by default + listUserShares := len(shareTypes) == 0 // if no share_types filter was set we want to list all share by default for _, s := range shareTypes { + if s == "" { + continue + } shareType, err := strconv.Atoi(strings.TrimSpace(s)) - if err != nil && s != "" { + if err != nil { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "invalid share type", err) return } - if s == "" || shareType == int(conversions.ShareTypeUser) || shareType == int(conversions.ShareTypeGroup) { - userShares, status, err := h.listUserShares(r, filters) - h.logProblems(status, err, "could not listUserShares") - shares = append(shares, userShares...) - } - if s == "" || shareType == int(conversions.ShareTypePublicLink) { - publicShares, status, err := h.listPublicShares(r, linkFilters) - h.logProblems(status, err, "could not listPublicShares") - shares = append(shares, publicShares...) + + switch shareType { + case int(conversions.ShareTypeUser): + listUserShares = true + filters = append(filters, share.UserGranteeFilter()) + case int(conversions.ShareTypeGroup): + listUserShares = true + filters = append(filters, share.GroupGranteeFilter()) + case int(conversions.ShareTypePublicLink): + listPublicShares = true } } + if listPublicShares { + publicShares, status, err := h.listPublicShares(r, linkFilters) + h.logProblems(status, err, "could not listPublicShares") + shares = append(shares, publicShares...) + } + if listUserShares { + userShares, status, err := h.listUserShares(r, filters) + h.logProblems(status, err, "could not listUserShares") + shares = append(shares, userShares...) + } + response.WriteOCSSuccess(w, r, shares) } diff --git a/pkg/share/manager/json/json.go b/pkg/share/manager/json/json.go index fe47803f94..0e4c4430e7 100644 --- a/pkg/share/manager/json/json.go +++ b/pkg/share/manager/json/json.go @@ -268,21 +268,15 @@ func (m *mgr) get(ctx context.Context, ref *collaboration.ShareReference) (s *co // check if we are the owner user := ctxpkg.ContextMustGetUser(ctx) - if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { + if share.IsCreatedByUser(s, user) { return s, nil } // or the grantee - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Grantee.GetUserId()) { + if share.IsGrantedToUser(s, user) { return s, nil - } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - // check if all user groups match this share; TODO(labkode): filter shares created by us. - for _, g := range user.Groups { - if g == s.Grantee.GetGroupId().OpaqueId { - return s, nil - } - } } + // we return not found to not disclose information return nil, errtypes.NotFound(ref.String()) } @@ -302,7 +296,7 @@ func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) er user := ctxpkg.ContextMustGetUser(ctx) for i, s := range m.model.Shares { if sharesEqual(ref, s) { - if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { + if share.IsCreatedByUser(s, user) { m.model.Shares[len(m.model.Shares)-1], m.model.Shares[i] = m.model.Shares[i], m.model.Shares[len(m.model.Shares)-1] m.model.Shares = m.model.Shares[:len(m.model.Shares)-1] if err := m.model.Save(); err != nil { @@ -336,7 +330,7 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference user := ctxpkg.ContextMustGetUser(ctx) for i, s := range m.model.Shares { if sharesEqual(ref, s) { - if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { + if share.IsCreatedByUser(s, user) { now := time.Now().UnixNano() m.model.Shares[i].Permissions = p m.model.Shares[i].Mtime = &typespb.Timestamp{ @@ -360,21 +354,23 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ( defer m.Unlock() user := ctxpkg.ContextMustGetUser(ctx) for _, s := range m.model.Shares { - if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { + if share.IsCreatedByUser(s, user) { // no filter we return earlier if len(filters) == 0 { ss = append(ss, s) - } else { - // check filters - // TODO(labkode): add the rest of filters. - for _, f := range filters { - if f.Type == collaboration.Filter_TYPE_RESOURCE_ID { - if utils.ResourceIDEqual(s.ResourceId, f.GetResourceId()) { - ss = append(ss, s) - } - } + continue + } + // check filters + allFiltersMatch := true + for _, f := range filters { + if !share.MatchesFilter(s, f) { + allFiltersMatch = false + break } } + if allFiltersMatch { + ss = append(ss, s) + } } } return ss, nil @@ -387,20 +383,21 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F defer m.Unlock() user := ctxpkg.ContextMustGetUser(ctx) for _, s := range m.model.Shares { - if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { - // omit shares created by me + if share.IsCreatedByUser(s, user) || !share.IsGrantedToUser(s, user) { + // omit shares created by the user or shares the user can't access continue } - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Grantee.GetUserId()) { + + if len(filters) == 0 { rs := m.convert(ctx, s) rss = append(rss, rs) - } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - // check if all user groups match this share; TODO(labkode): filter shares created by us. - for _, g := range user.Groups { - if g == s.Grantee.GetGroupId().OpaqueId { - rs := m.convert(ctx, s) - rss = append(rss, rs) - } + continue + } + + for _, f := range filters { + if share.MatchesFilter(s, f) { + rs := m.convert(ctx, s) + rss = append(rss, rs) } } } @@ -432,16 +429,9 @@ func (m *mgr) getReceived(ctx context.Context, ref *collaboration.ShareReference user := ctxpkg.ContextMustGetUser(ctx) for _, s := range m.model.Shares { if sharesEqual(ref, s) { - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Grantee.GetUserId()) { + if share.IsGrantedToUser(s, user) { rs := m.convert(ctx, s) return rs, nil - } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - for _, g := range user.Groups { - if s.Grantee.GetGroupId().OpaqueId == g { - rs := m.convert(ctx, s) - return rs, nil - } - } } } } diff --git a/pkg/share/manager/memory/memory.go b/pkg/share/manager/memory/memory.go index 8f84c77d9b..40a9417c52 100644 --- a/pkg/share/manager/memory/memory.go +++ b/pkg/share/manager/memory/memory.go @@ -148,7 +148,7 @@ func (m *manager) get(ctx context.Context, ref *collaboration.ShareReference) (s // check if we are the owner user := ctxpkg.ContextMustGetUser(ctx) - if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { + if share.IsCreatedByUser(s, user) { return s, nil } @@ -171,7 +171,7 @@ func (m *manager) Unshare(ctx context.Context, ref *collaboration.ShareReference user := ctxpkg.ContextMustGetUser(ctx) for i, s := range m.shares { if sharesEqual(ref, s) { - if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { + if share.IsCreatedByUser(s, user) { m.shares[len(m.shares)-1], m.shares[i] = m.shares[i], m.shares[len(m.shares)-1] m.shares = m.shares[:len(m.shares)-1] return nil @@ -201,7 +201,7 @@ func (m *manager) UpdateShare(ctx context.Context, ref *collaboration.ShareRefer user := ctxpkg.ContextMustGetUser(ctx) for i, s := range m.shares { if sharesEqual(ref, s) { - if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { + if share.IsCreatedByUser(s, user) { now := time.Now().UnixNano() m.shares[i].Permissions = p m.shares[i].Mtime = &typespb.Timestamp{ @@ -221,21 +221,23 @@ func (m *manager) ListShares(ctx context.Context, filters []*collaboration.Filte defer m.lock.Unlock() user := ctxpkg.ContextMustGetUser(ctx) for _, s := range m.shares { - if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { + if share.IsCreatedByUser(s, user) { // no filter we return earlier if len(filters) == 0 { ss = append(ss, s) - } else { - // check filters - // TODO(labkode): add the rest of filters. - for _, f := range filters { - if f.Type == collaboration.Filter_TYPE_RESOURCE_ID { - if utils.ResourceIDEqual(s.ResourceId, f.GetResourceId()) { - ss = append(ss, s) - } - } + continue + } + // check filters + allFiltersMatch := true + for _, f := range filters { + if !share.MatchesFilter(s, f) { + allFiltersMatch = false + break } } + if allFiltersMatch { + ss = append(ss, s) + } } } return ss, nil @@ -248,20 +250,21 @@ func (m *manager) ListReceivedShares(ctx context.Context, filters []*collaborati defer m.lock.Unlock() user := ctxpkg.ContextMustGetUser(ctx) for _, s := range m.shares { - if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { - // omit shares created by me + if share.IsCreatedByUser(s, user) || !share.IsGrantedToUser(s, user) { + // omit shares created by the user or shares the user can't access continue } - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Grantee.GetUserId()) { + + if len(filters) == 0 { rs := m.convert(ctx, s) rss = append(rss, rs) - } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - // check if all user groups match this share; TODO(labkode): filter shares created by us. - for _, g := range user.Groups { - if g == s.Grantee.GetGroupId().OpaqueId { - rs := m.convert(ctx, s) - rss = append(rss, rs) - } + continue + } + + for _, f := range filters { + if share.MatchesFilter(s, f) { + rs := m.convert(ctx, s) + rss = append(rss, rs) } } } @@ -293,16 +296,9 @@ func (m *manager) getReceived(ctx context.Context, ref *collaboration.ShareRefer user := ctxpkg.ContextMustGetUser(ctx) for _, s := range m.shares { if sharesEqual(ref, s) { - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Grantee.GetUserId()) { + if share.IsGrantedToUser(s, user) { rs := m.convert(ctx, s) return rs, nil - } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - for _, g := range user.Groups { - if s.Grantee.GetGroupId().OpaqueId == g { - rs := m.convert(ctx, s) - return rs, nil - } - } } } } diff --git a/pkg/share/share.go b/pkg/share/share.go index 9184e0d93c..53eb398e76 100644 --- a/pkg/share/share.go +++ b/pkg/share/share.go @@ -21,8 +21,10 @@ package share import ( "context" + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/utils" ) // Manager is the interface that manipulates shares. @@ -82,3 +84,36 @@ func ResourceIDFilter(id *provider.ResourceId) *collaboration.Filter { }, } } + +// IsCreatedByUser checks if the user is the owner or creator of the share. +func IsCreatedByUser(share *collaboration.Share, user *userv1beta1.User) bool { + return utils.UserEqual(user.Id, share.Owner) || utils.UserEqual(user.Id, share.Creator) +} + +// IsGrantedToUser checks if the user is a grantee of the share. Either by a user grant or by a group grant. +func IsGrantedToUser(share *collaboration.Share, user *userv1beta1.User) bool { + if share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, share.Grantee.GetUserId()) { + return true + } + if share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { + // check if any of the user's group is the grantee of the share + for _, g := range user.Groups { + if g == share.Grantee.GetGroupId().OpaqueId { + return true + } + } + } + return false +} + +// MatchesFilter tests if the share passes the filter. +func MatchesFilter(share *collaboration.Share, filter *collaboration.Filter) bool { + switch filter.Type { + case collaboration.Filter_TYPE_RESOURCE_ID: + return utils.ResourceIDEqual(share.ResourceId, filter.GetResourceId()) + case collaboration.Filter_TYPE_GRANTEE_TYPE: + return share.Grantee.Type == filter.GetGranteeType() + default: + return false + } +} diff --git a/pkg/share/share_test.go b/pkg/share/share_test.go new file mode 100644 index 0000000000..a88b96abbd --- /dev/null +++ b/pkg/share/share_test.go @@ -0,0 +1,119 @@ +// Copyright 2018-2021 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 share + +import ( + "testing" + + groupv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +func TestIsCreatedByUser(t *testing.T) { + user := userv1beta1.User{ + Id: &userv1beta1.UserId{ + Idp: "sampleidp", + OpaqueId: "user", + }, + } + + s1 := collaborationv1beta1.Share{ + Owner: &userv1beta1.UserId{ + Idp: "sampleidp", + OpaqueId: "user", + }, + } + + s2 := collaborationv1beta1.Share{ + Creator: &userv1beta1.UserId{ + Idp: "sampleidp", + OpaqueId: "user", + }, + } + + s3 := collaborationv1beta1.Share{ + Owner: &userv1beta1.UserId{ + Idp: "sampleidp", + OpaqueId: "user", + }, + Creator: &userv1beta1.UserId{ + Idp: "sampleidp", + OpaqueId: "user", + }, + } + + if !IsCreatedByUser(&s1, &user) || !IsCreatedByUser(&s2, &user) || !IsCreatedByUser(&s3, &user) { + t.Error("Expected share to be created by user") + } + + anotherUser := userv1beta1.User{ + Id: &userv1beta1.UserId{ + Idp: "sampleidp", + OpaqueId: "another", + }, + } + + if IsCreatedByUser(&s1, &anotherUser) { + t.Error("Expected share not to be created by user") + } +} + +func TestIsGrantedToUser(t *testing.T) { + user := userv1beta1.User{ + Id: &userv1beta1.UserId{ + Idp: "sampleidp", + OpaqueId: "another", + }, + Groups: []string{"groupid"}, + } + + s1 := collaborationv1beta1.Share{ + Grantee: &providerv1beta1.Grantee{ + Type: providerv1beta1.GranteeType_GRANTEE_TYPE_USER, + Id: &providerv1beta1.Grantee_UserId{ + UserId: &userv1beta1.UserId{ + Idp: "sampleidp", + OpaqueId: "another", + }, + }, + }, + } + + s2 := collaborationv1beta1.Share{ + Grantee: &providerv1beta1.Grantee{ + Type: providerv1beta1.GranteeType_GRANTEE_TYPE_GROUP, + Id: &providerv1beta1.Grantee_GroupId{ + GroupId: &groupv1beta1.GroupId{OpaqueId: "groupid"}}, + }, + } + + if !IsGrantedToUser(&s1, &user) || !IsGrantedToUser(&s2, &user) { + t.Error("Expected the share to be granted to user") + } + + s3 := collaborationv1beta1.Share{ + Grantee: &providerv1beta1.Grantee{}, + } + + if IsGrantedToUser(&s3, &user) { + t.Error("Expecte the share not to be granted to user") + } +} diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index cabfea6de7..21f0eeb329 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -374,33 +374,6 @@ The first two tests work against ocis. There must be something wrong in the CI s - [apiShareOperationsToShares1/gettingShares.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L221) - [apiShareOperationsToShares1/gettingShares.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L222) -#### [Allow getting the share list filtered by share type via API](https://github.com/owncloud/ocis/issues/774) - -- [apiShareOperationsToShares1/gettingSharesPendingFiltered.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesPendingFiltered.feature#L44) -- [apiShareOperationsToShares1/gettingSharesPendingFiltered.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesPendingFiltered.feature#L45) -- [apiShareOperationsToShares1/gettingSharesPendingFiltered.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesPendingFiltered.feature#L56) -- [apiShareOperationsToShares1/gettingSharesPendingFiltered.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesPendingFiltered.feature#L57) -- [apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature#L47) -- [apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature#L48) -- [apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature#L60) -- [apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature#L61) -- [apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature#L41) -- [apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature#L42) -- [apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature#L62) -- [apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature#L63) -- [apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature#L90) -- [apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature#L91) -- [apiShareOperationsToShares1/gettingSharesSharedFiltered.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFiltered.feature#L48) -- [apiShareOperationsToShares1/gettingSharesSharedFiltered.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFiltered.feature#L49) -- [apiShareOperationsToShares1/gettingSharesSharedFiltered.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFiltered.feature#L62) -- [apiShareOperationsToShares1/gettingSharesSharedFiltered.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFiltered.feature#L63) -- [apiShareOperationsToShares1/gettingSharesSharedFiltered.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFiltered.feature#L92) -- [apiShareOperationsToShares1/gettingSharesSharedFiltered.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFiltered.feature#L93) -- [apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature#L39) -- [apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature#L40) -- [apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature#L60) -- [apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature#L61) - #### [Public link enforce permissions](https://github.com/owncloud/ocis/issues/1269) - [apiSharePublicLink1/accessToPublicLinkShare.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/accessToPublicLinkShare.feature#L10) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 527c5e9006..7bc2e02832 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -388,33 +388,6 @@ File and sync features in a shared scenario - [apiShareOperationsToShares1/gettingShares.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L221) - [apiShareOperationsToShares1/gettingShares.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L222) -#### [Allow getting the share list filtered by share type via API](https://github.com/owncloud/ocis/issues/774) - -- [apiShareOperationsToShares1/gettingSharesPendingFiltered.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesPendingFiltered.feature#L44) -- [apiShareOperationsToShares1/gettingSharesPendingFiltered.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesPendingFiltered.feature#L45) -- [apiShareOperationsToShares1/gettingSharesPendingFiltered.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesPendingFiltered.feature#L56) -- [apiShareOperationsToShares1/gettingSharesPendingFiltered.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesPendingFiltered.feature#L57) -- [apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature#L47) -- [apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature#L48) -- [apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature#L60) -- [apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFiltered.feature#L61) -- [apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature#L41) -- [apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature#L42) -- [apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature#L62) -- [apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature#L63) -- [apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature#L90) -- [apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature#L91) -- [apiShareOperationsToShares1/gettingSharesSharedFiltered.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFiltered.feature#L48) -- [apiShareOperationsToShares1/gettingSharesSharedFiltered.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFiltered.feature#L49) -- [apiShareOperationsToShares1/gettingSharesSharedFiltered.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFiltered.feature#L62) -- [apiShareOperationsToShares1/gettingSharesSharedFiltered.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFiltered.feature#L63) -- [apiShareOperationsToShares1/gettingSharesSharedFiltered.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFiltered.feature#L92) -- [apiShareOperationsToShares1/gettingSharesSharedFiltered.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFiltered.feature#L93) -- [apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature#L39) -- [apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature#L40) -- [apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature#L60) -- [apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature#L61) - #### [Public link enforce permissions](https://github.com/owncloud/ocis/issues/1269) - [apiSharePublicLink1/accessToPublicLinkShare.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/accessToPublicLinkShare.feature#L10) From 9a2ed3a6a5a033a553f9bfe1550a9bcbd5ee0bb6 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Wed, 8 Sep 2021 11:29:00 +0200 Subject: [PATCH 2/3] enhance filter handling in the sql share drivers --- pkg/cbox/share/sql/sql.go | 122 ++++++++++++++++++++++++----- pkg/share/manager/json/json.go | 11 ++- pkg/share/manager/memory/memory.go | 12 ++- pkg/share/manager/sql/sql.go | 114 ++++++++++++++++++++++++--- pkg/share/share.go | 5 ++ 5 files changed, 227 insertions(+), 37 deletions(-) diff --git a/pkg/cbox/share/sql/sql.go b/pkg/cbox/share/sql/sql.go index 675f40ba0a..5e786db4fa 100644 --- a/pkg/cbox/share/sql/sql.go +++ b/pkg/cbox/share/sql/sql.go @@ -43,6 +43,11 @@ import ( _ "github.com/go-sql-driver/mysql" ) +const ( + shareTypeUser = 0 + shareTypeGroup = 1 +) + func init() { registry.Register("sql", New) } @@ -276,19 +281,30 @@ 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, id, stime, permissions, share_type + 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, id, stime, permissions, share_type FROM oc_share - WHERE (orphan = 0 or orphan IS NULL) AND (uid_owner=? or uid_initiator=?) AND (share_type=? OR share_type=?)` - params := []interface{}{uid, uid, 0, 1} - for _, f := range filters { - if f.Type == collaboration.Filter_TYPE_RESOURCE_ID { - query += " AND (fileid_prefix=? AND item_source=?)" - params = append(params, f.GetResourceId().StorageId, f.GetResourceId().OpaqueId) - } else if f.Type == collaboration.Filter_TYPE_EXCLUDE_DENIALS { - // TODO this may change once the mapping of permission to share types is completed (cf. pkg/cbox/utils/conversions.go) - query += " AND (permissions > 0)" + WHERE (orphan = 0 or orphan IS NULL) AND (uid_owner=? or uid_initiator=?)` + params := []interface{}{uid, uid} + var ( + filterQuery string + filterParams []interface{} + err error + ) + if len(filters) == 0 { + filterQuery += "(share_type=? OR share_type=?)" + params = append(params, shareTypeUser) + params = append(params, shareTypeGroup) + } else { + filterQuery, filterParams, err = translateFilters(filters) + if err != nil { + return nil, err } + params = append(params, filterParams...) + } + + if filterQuery != "" { + query = fmt.Sprintf("%s AND (%s)", query, filterQuery) } rows, err := m.db.Query(query, params...) @@ -333,13 +349,14 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F query += " AND (share_with=?)" } - for _, f := range filters { - if f.Type == collaboration.Filter_TYPE_RESOURCE_ID { - query += " AND (fileid_prefix=? AND item_source=?)" - params = append(params, f.GetResourceId().StorageId, f.GetResourceId().OpaqueId) - } else if f.Type == collaboration.Filter_TYPE_EXCLUDE_DENIALS { - query += " AND (permissions > 0)" - } + filterQuery, filterParams, err := translateFilters(filters) + if err != nil { + return nil, err + } + params = append(params, filterParams...) + + if filterQuery != "" { + query = fmt.Sprintf("%s AND (%s)", query, filterQuery) } rows, err := m.db.Query(query, params...) @@ -476,3 +493,72 @@ func (m *mgr) UpdateReceivedShare(ctx context.Context, ref *collaboration.ShareR rs.State = f.GetState() return rs, nil } + +func groupFiltersByType(filters []*collaboration.Filter) map[collaboration.Filter_Type][]*collaboration.Filter { + grouped := make(map[collaboration.Filter_Type][]*collaboration.Filter) + for _, f := range filters { + grouped[f.Type] = append(grouped[f.Type], f) + } + return grouped +} + +func granteeTypeToShareType(granteeType provider.GranteeType) int { + switch granteeType { + case provider.GranteeType_GRANTEE_TYPE_USER: + return shareTypeUser + case provider.GranteeType_GRANTEE_TYPE_GROUP: + return shareTypeGroup + } + return -1 +} + +// translateFilters translates the filters to sql queries +func translateFilters(filters []*collaboration.Filter) (string, []interface{}, error) { + var ( + filterQuery string + params []interface{} + ) + + groupedFilters := 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 { + switch filterType { + case collaboration.Filter_TYPE_RESOURCE_ID: + filterQuery += "(" + for i, f := range filters { + filterQuery += "(fileid_prefix =? AND item_source=?" + params = append(params, f.GetResourceId().StorageId, f.GetResourceId().OpaqueId) + + if i != len(filters)-1 { + filterQuery += " OR " + } + } + filterQuery += ")" + case collaboration.Filter_TYPE_GRANTEE_TYPE: + filterQuery += "(" + for i, f := range filters { + filterQuery += "share_type=?" + params = append(params, granteeTypeToShareType(f.GetGranteeType())) + + if i != len(filters)-1 { + filterQuery += " OR " + } + } + filterQuery += ")" + case collaboration.Filter_TYPE_EXCLUDE_DENIALS: + // TODO this may change once the mapping of permission to share types is completed (cf. pkg/cbox/utils/conversions.go) + filterQuery += "(permissions > 0)" + default: + return "", nil, fmt.Errorf("filter type is not supported") + } + if filterCounter != len(groupedFilters)-1 { + filterQuery += " AND " + } + filterCounter++ + } + return filterQuery, params, nil +} diff --git a/pkg/share/manager/json/json.go b/pkg/share/manager/json/json.go index 0e4c4430e7..45c2494b65 100644 --- a/pkg/share/manager/json/json.go +++ b/pkg/share/manager/json/json.go @@ -394,12 +394,17 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F continue } + allFiltersMatch := true for _, f := range filters { - if share.MatchesFilter(s, f) { - rs := m.convert(ctx, s) - rss = append(rss, rs) + if !share.MatchesFilter(s, f) { + allFiltersMatch = false + break } } + if allFiltersMatch { + rs := m.convert(ctx, s) + rss = append(rss, rs) + } } return rss, nil } diff --git a/pkg/share/manager/memory/memory.go b/pkg/share/manager/memory/memory.go index 40a9417c52..fc86422093 100644 --- a/pkg/share/manager/memory/memory.go +++ b/pkg/share/manager/memory/memory.go @@ -260,13 +260,17 @@ func (m *manager) ListReceivedShares(ctx context.Context, filters []*collaborati rss = append(rss, rs) continue } - + allFiltersMatch := true for _, f := range filters { - if share.MatchesFilter(s, f) { - rs := m.convert(ctx, s) - rss = append(rss, rs) + if !share.MatchesFilter(s, f) { + allFiltersMatch = false + break } } + if allFiltersMatch { + rs := m.convert(ctx, s) + rss = append(rss, rs) + } } return rss, nil } diff --git a/pkg/share/manager/sql/sql.go b/pkg/share/manager/sql/sql.go index c43ef2b377..d935dd796c 100644 --- a/pkg/share/manager/sql/sql.go +++ b/pkg/share/manager/sql/sql.go @@ -42,6 +42,11 @@ import ( _ "github.com/go-sql-driver/mysql" ) +const ( + shareTypeUser = 0 + shareTypeGroup = 1 +) + func init() { registry.Register("oc10-sql", NewMysql) } @@ -271,20 +276,26 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { uid := ctxpkg.ContextMustGetUser(ctx).Username - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(item_source, '') as item_source, id, stime, permissions, share_type FROM oc_share WHERE (uid_owner=? or uid_initiator=?) AND (share_type=? OR share_type=?)" - var filterQuery string - params := []interface{}{uid, uid, 0, 1} - for i, f := range filters { - if f.Type == collaboration.Filter_TYPE_RESOURCE_ID { - filterQuery += "(item_source=?)" - if i != len(filters)-1 { - filterQuery += " AND " - } - params = append(params, f.GetResourceId().OpaqueId) - } else { - return nil, fmt.Errorf("filter type is not supported") + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(item_source, '') as item_source, id, stime, permissions, share_type FROM oc_share WHERE (uid_owner=? or uid_initiator=?)" + params := []interface{}{uid, uid} + + var ( + filterQuery string + filterParams []interface{} + err error + ) + if len(filters) == 0 { + filterQuery += "(share_type=? OR share_type=?)" + params = append(params, shareTypeUser) + params = append(params, shareTypeGroup) + } else { + filterQuery, filterParams, err = translateFilters(filters) + if err != nil { + return nil, err } + params = append(params, filterParams...) } + if filterQuery != "" { query = fmt.Sprintf("%s AND (%s)", query, filterQuery) } @@ -337,6 +348,16 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F query += "AND (share_with=?)" } + filterQuery, filterParams, err := translateFilters(filters) + if err != nil { + return nil, err + } + params = append(params, filterParams...) + + if filterQuery != "" { + query = fmt.Sprintf("%s AND (%s)", query, filterQuery) + } + rows, err := m.db.Query(query, params...) if err != nil { return nil, err @@ -500,3 +521,72 @@ func (m *mgr) getReceivedByKey(ctx context.Context, key *collaboration.ShareKey) } return m.convertToCS3ReceivedShare(ctx, s, m.storageMountID) } + +func groupFiltersByType(filters []*collaboration.Filter) map[collaboration.Filter_Type][]*collaboration.Filter { + grouped := make(map[collaboration.Filter_Type][]*collaboration.Filter) + for _, f := range filters { + grouped[f.Type] = append(grouped[f.Type], f) + } + return grouped +} + +func granteeTypeToShareType(granteeType provider.GranteeType) int { + switch granteeType { + case provider.GranteeType_GRANTEE_TYPE_USER: + return shareTypeUser + case provider.GranteeType_GRANTEE_TYPE_GROUP: + return shareTypeGroup + } + return -1 +} + +// translateFilters translates the filters to sql queries +func translateFilters(filters []*collaboration.Filter) (string, []interface{}, error) { + var ( + filterQuery string + params []interface{} + ) + + groupedFilters := 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 { + switch filterType { + case collaboration.Filter_TYPE_RESOURCE_ID: + filterQuery += "(" + for i, f := range filters { + filterQuery += "item_source=?" + params = append(params, f.GetResourceId().OpaqueId) + + if i != len(filters)-1 { + filterQuery += " OR " + } + } + filterQuery += ")" + case collaboration.Filter_TYPE_GRANTEE_TYPE: + filterQuery += "(" + for i, f := range filters { + filterQuery += "share_type=?" + params = append(params, granteeTypeToShareType(f.GetGranteeType())) + + if i != len(filters)-1 { + filterQuery += " OR " + } + } + filterQuery += ")" + case collaboration.Filter_TYPE_EXCLUDE_DENIALS: + // TODO this may change once the mapping of permission to share types is completed (cf. pkg/cbox/utils/conversions.go) + filterQuery += "permissions > 0" + default: + return "", nil, fmt.Errorf("filter type is not supported") + } + if filterCounter != len(groupedFilters)-1 { + filterQuery += " AND " + } + filterCounter++ + } + return filterQuery, params, nil +} diff --git a/pkg/share/share.go b/pkg/share/share.go index 53eb398e76..1da5efa3e0 100644 --- a/pkg/share/share.go +++ b/pkg/share/share.go @@ -24,6 +24,7 @@ import ( userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/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/utils" ) @@ -113,6 +114,10 @@ func MatchesFilter(share *collaboration.Share, filter *collaboration.Filter) boo return utils.ResourceIDEqual(share.ResourceId, filter.GetResourceId()) case collaboration.Filter_TYPE_GRANTEE_TYPE: return share.Grantee.Type == filter.GetGranteeType() + case collaboration.Filter_TYPE_EXCLUDE_DENIALS: + // This filter type is used to filter out "denial shares". These are currently implemented by having the permission "0". + // I.e. if the permission is 0 we don't want to show it. + return int(conversions.RoleFromResourcePermissions(share.Permissions.Permissions).OCSPermissions()) != 0 default: return false } From d9dfebe162559cb3ebcbb2761ebbb0f8a2483567 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Tue, 5 Oct 2021 13:49:16 +0200 Subject: [PATCH 3/3] correct and simplify filter checks --- pkg/cbox/share/sql/sql.go | 10 +-- pkg/share/manager/json/json.go | 18 +---- pkg/share/manager/memory/memory.go | 19 +---- pkg/share/manager/sql/sql.go | 10 +-- pkg/share/share.go | 33 ++++++++ pkg/share/share_test.go | 124 +++++++++++++++++++++++++---- 6 files changed, 149 insertions(+), 65 deletions(-) diff --git a/pkg/cbox/share/sql/sql.go b/pkg/cbox/share/sql/sql.go index 5e786db4fa..90c5f9786e 100644 --- a/pkg/cbox/share/sql/sql.go +++ b/pkg/cbox/share/sql/sql.go @@ -494,14 +494,6 @@ func (m *mgr) UpdateReceivedShare(ctx context.Context, ref *collaboration.ShareR return rs, nil } -func groupFiltersByType(filters []*collaboration.Filter) map[collaboration.Filter_Type][]*collaboration.Filter { - grouped := make(map[collaboration.Filter_Type][]*collaboration.Filter) - for _, f := range filters { - grouped[f.Type] = append(grouped[f.Type], f) - } - return grouped -} - func granteeTypeToShareType(granteeType provider.GranteeType) int { switch granteeType { case provider.GranteeType_GRANTEE_TYPE_USER: @@ -519,7 +511,7 @@ func translateFilters(filters []*collaboration.Filter) (string, []interface{}, e params []interface{} ) - groupedFilters := groupFiltersByType(filters) + 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. diff --git a/pkg/share/manager/json/json.go b/pkg/share/manager/json/json.go index 45c2494b65..b0ddd401d4 100644 --- a/pkg/share/manager/json/json.go +++ b/pkg/share/manager/json/json.go @@ -361,14 +361,7 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ( continue } // check filters - allFiltersMatch := true - for _, f := range filters { - if !share.MatchesFilter(s, f) { - allFiltersMatch = false - break - } - } - if allFiltersMatch { + if share.MatchesFilters(s, filters) { ss = append(ss, s) } } @@ -394,14 +387,7 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F continue } - allFiltersMatch := true - for _, f := range filters { - if !share.MatchesFilter(s, f) { - allFiltersMatch = false - break - } - } - if allFiltersMatch { + if share.MatchesFilters(s, filters) { rs := m.convert(ctx, s) rss = append(rss, rs) } diff --git a/pkg/share/manager/memory/memory.go b/pkg/share/manager/memory/memory.go index fc86422093..13c808be76 100644 --- a/pkg/share/manager/memory/memory.go +++ b/pkg/share/manager/memory/memory.go @@ -228,14 +228,7 @@ func (m *manager) ListShares(ctx context.Context, filters []*collaboration.Filte continue } // check filters - allFiltersMatch := true - for _, f := range filters { - if !share.MatchesFilter(s, f) { - allFiltersMatch = false - break - } - } - if allFiltersMatch { + if share.MatchesFilters(s, filters) { ss = append(ss, s) } } @@ -260,14 +253,8 @@ func (m *manager) ListReceivedShares(ctx context.Context, filters []*collaborati rss = append(rss, rs) continue } - allFiltersMatch := true - for _, f := range filters { - if !share.MatchesFilter(s, f) { - allFiltersMatch = false - break - } - } - if allFiltersMatch { + + if share.MatchesFilters(s, filters) { rs := m.convert(ctx, s) rss = append(rss, rs) } diff --git a/pkg/share/manager/sql/sql.go b/pkg/share/manager/sql/sql.go index d935dd796c..f6be2752d2 100644 --- a/pkg/share/manager/sql/sql.go +++ b/pkg/share/manager/sql/sql.go @@ -522,14 +522,6 @@ func (m *mgr) getReceivedByKey(ctx context.Context, key *collaboration.ShareKey) return m.convertToCS3ReceivedShare(ctx, s, m.storageMountID) } -func groupFiltersByType(filters []*collaboration.Filter) map[collaboration.Filter_Type][]*collaboration.Filter { - grouped := make(map[collaboration.Filter_Type][]*collaboration.Filter) - for _, f := range filters { - grouped[f.Type] = append(grouped[f.Type], f) - } - return grouped -} - func granteeTypeToShareType(granteeType provider.GranteeType) int { switch granteeType { case provider.GranteeType_GRANTEE_TYPE_USER: @@ -547,7 +539,7 @@ func translateFilters(filters []*collaboration.Filter) (string, []interface{}, e params []interface{} ) - groupedFilters := groupFiltersByType(filters) + 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. diff --git a/pkg/share/share.go b/pkg/share/share.go index 1da5efa3e0..e5b69ff577 100644 --- a/pkg/share/share.go +++ b/pkg/share/share.go @@ -122,3 +122,36 @@ func MatchesFilter(share *collaboration.Share, filter *collaboration.Filter) boo return false } } + +// MatchesAnyFilter checks if the share passes at least one of the given filters. +func MatchesAnyFilter(share *collaboration.Share, filters []*collaboration.Filter) bool { + for _, f := range filters { + if MatchesFilter(share, f) { + return true + } + } + return false +} + +// MatchesFilters checks if the share passes the given filters. +// Filters of the same type form a disjuntion, a logical OR. Filters of separate type form a conjunction, a logical AND. +// Here is an example: +// (resource_id=1 OR resource_id=2) AND (grantee_type=USER OR grantee_type=GROUP) +func MatchesFilters(share *collaboration.Share, filters []*collaboration.Filter) bool { + grouped := GroupFiltersByType(filters) + for _, f := range grouped { + if !MatchesAnyFilter(share, f) { + return false + } + } + return true +} + +// GroupFiltersByType groups the given filters and returns a map using the filter type as the key. +func GroupFiltersByType(filters []*collaboration.Filter) map[collaboration.Filter_Type][]*collaboration.Filter { + grouped := make(map[collaboration.Filter_Type][]*collaboration.Filter) + for _, f := range filters { + grouped[f.Type] = append(grouped[f.Type], f) + } + return grouped +} diff --git a/pkg/share/share_test.go b/pkg/share/share_test.go index a88b96abbd..ee6b0533f8 100644 --- a/pkg/share/share_test.go +++ b/pkg/share/share_test.go @@ -23,8 +23,8 @@ import ( groupv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" - providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" ) func TestIsCreatedByUser(t *testing.T) { @@ -35,21 +35,21 @@ func TestIsCreatedByUser(t *testing.T) { }, } - s1 := collaborationv1beta1.Share{ + s1 := collaboration.Share{ Owner: &userv1beta1.UserId{ Idp: "sampleidp", OpaqueId: "user", }, } - s2 := collaborationv1beta1.Share{ + s2 := collaboration.Share{ Creator: &userv1beta1.UserId{ Idp: "sampleidp", OpaqueId: "user", }, } - s3 := collaborationv1beta1.Share{ + s3 := collaboration.Share{ Owner: &userv1beta1.UserId{ Idp: "sampleidp", OpaqueId: "user", @@ -85,10 +85,10 @@ func TestIsGrantedToUser(t *testing.T) { Groups: []string{"groupid"}, } - s1 := collaborationv1beta1.Share{ - Grantee: &providerv1beta1.Grantee{ - Type: providerv1beta1.GranteeType_GRANTEE_TYPE_USER, - Id: &providerv1beta1.Grantee_UserId{ + s1 := collaboration.Share{ + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{ UserId: &userv1beta1.UserId{ Idp: "sampleidp", OpaqueId: "another", @@ -97,10 +97,10 @@ func TestIsGrantedToUser(t *testing.T) { }, } - s2 := collaborationv1beta1.Share{ - Grantee: &providerv1beta1.Grantee{ - Type: providerv1beta1.GranteeType_GRANTEE_TYPE_GROUP, - Id: &providerv1beta1.Grantee_GroupId{ + s2 := collaboration.Share{ + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_GROUP, + Id: &provider.Grantee_GroupId{ GroupId: &groupv1beta1.GroupId{OpaqueId: "groupid"}}, }, } @@ -109,11 +109,105 @@ func TestIsGrantedToUser(t *testing.T) { t.Error("Expected the share to be granted to user") } - s3 := collaborationv1beta1.Share{ - Grantee: &providerv1beta1.Grantee{}, + s3 := collaboration.Share{ + Grantee: &provider.Grantee{}, } if IsGrantedToUser(&s3, &user) { t.Error("Expecte the share not to be granted to user") } } + +func TestMatchesFilter(t *testing.T) { + id := &provider.ResourceId{StorageId: "storage", OpaqueId: "opaque"} + share := &collaboration.Share{ + ResourceId: id, + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + }, + Permissions: &collaboration.SharePermissions{Permissions: &provider.ResourcePermissions{}}, + } + + if !MatchesFilter(share, ResourceIDFilter(id)) { + t.Errorf("Expected share to pass the id filter. Share: %v", share) + } + if MatchesFilter(share, GroupGranteeFilter()) { + t.Errorf("Expected share to not pass the grantee type filter. Share: %v", share) + } + if MatchesFilter(share, &collaboration.Filter{Type: collaboration.Filter_TYPE_EXCLUDE_DENIALS}) { + t.Errorf("Expected share to not pass the exclude denials filter. Share: %v", share) + } + if MatchesFilter(share, &collaboration.Filter{Type: collaboration.Filter_TYPE_INVALID}) { + t.Errorf("Expected share to not pass an invalid filter. Share: %v", share) + } +} + +func TestMatchesAnyFilter(t *testing.T) { + id := &provider.ResourceId{StorageId: "storage", OpaqueId: "opaque"} + share := &collaboration.Share{ + ResourceId: id, + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + }, + Permissions: &collaboration.SharePermissions{Permissions: &provider.ResourcePermissions{}}, + } + + f1 := []*collaboration.Filter{UserGranteeFilter(), GroupGranteeFilter()} + if !MatchesAnyFilter(share, f1) { + t.Errorf("Expected share to match any of the given filters. Share: %v, Filters: %v", share, f1) + } + + f2 := []*collaboration.Filter{ResourceIDFilter(&provider.ResourceId{StorageId: "something", OpaqueId: "different"}), GroupGranteeFilter()} + if MatchesAnyFilter(share, f2) { + t.Errorf("Expected share to not match any of the given filters. Share: %v, Filters: %v", share, f2) + } +} + +func TestMatchesFilters(t *testing.T) { + id := &provider.ResourceId{StorageId: "storage", OpaqueId: "opaque"} + share := &collaboration.Share{ + ResourceId: id, + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + }, + Permissions: &collaboration.SharePermissions{Permissions: &provider.ResourcePermissions{}}, + } + + f1 := []*collaboration.Filter{ResourceIDFilter(id), GroupGranteeFilter()} + if MatchesFilters(share, f1) { + t.Errorf("Expected share to not match the filters. Share %v, Filters %v", share, f1) + } + + f2 := []*collaboration.Filter{ResourceIDFilter(id), UserGranteeFilter(), GroupGranteeFilter()} + if !MatchesFilters(share, f2) { + t.Errorf("Expected share to match the filters. Share %v, Filters %v", share, f2) + } +} + +func TestGroupFiltersByType(t *testing.T) { + id := &provider.ResourceId{StorageId: "storage", OpaqueId: "opaque"} + filters := []*collaboration.Filter{UserGranteeFilter(), GroupGranteeFilter(), ResourceIDFilter(id)} + + grouped := GroupFiltersByType(filters) + + for fType, f := range grouped { + switch fType { + case collaboration.Filter_TYPE_GRANTEE_TYPE: + if len(f) != 2 { + t.Errorf("Expected 2 grantee type filters got %d", len(f)) + } + for i := range f { + if f[i].Type != fType { + t.Errorf("Filter %v doesn't belong to this type %v", f[i], t) + } + } + case collaboration.Filter_TYPE_RESOURCE_ID: + if len(f) != 1 { + t.Errorf("Expected 1 resource id filter got %d", len(f)) + } + if f[0].Type != fType { + t.Errorf("Filter %v doesn't belong to this type %v", f[0], t) + } + } + } +}