From ca7621cfb047646a5da2b795550adf10dea301cc Mon Sep 17 00:00:00 2001 From: Jian Qiu Date: Tue, 20 Feb 2024 09:53:19 +0800 Subject: [PATCH] fix: filter organization nodes by policy tags (#19507) Co-authored-by: Qiu Jian --- pkg/apis/identity/organization.go | 7 + pkg/keystone/models/organization_nodes.go | 44 +++++- pkg/keystone/models/projects.go | 2 - pkg/util/tagutils/tagset.go | 33 ++++ pkg/util/tagutils/tagset_test.go | 174 ++++++++++++++++++++++ 5 files changed, 257 insertions(+), 3 deletions(-) diff --git a/pkg/apis/identity/organization.go b/pkg/apis/identity/organization.go index 9c43101764..77c94d056b 100644 --- a/pkg/apis/identity/organization.go +++ b/pkg/apis/identity/organization.go @@ -180,6 +180,13 @@ type OrganizationNodeListInput struct { OrgType TOrgType `json:"org_type"` Level int `json:"level"` + + // domain tags filter imposed by policy + PolicyDomainTags tagutils.TTagSetList `json:"policy_domain_tags"` + // project tags filter imposed by policy + PolicyProjectTags tagutils.TTagSetList `json:"policy_project_tags"` + // object tags filter imposed by policy + PolicyObjectTags tagutils.TTagSetList `json:"policy_object_tags"` } type SProjectOrganization struct { diff --git a/pkg/keystone/models/organization_nodes.go b/pkg/keystone/models/organization_nodes.go index 1c34dcc357..7b4a835f02 100644 --- a/pkg/keystone/models/organization_nodes.go +++ b/pkg/keystone/models/organization_nodes.go @@ -20,6 +20,7 @@ import ( "database/sql" "encoding/binary" "fmt" + "strings" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -277,6 +278,27 @@ func (orgNode *SOrganizationNode) ValidateUpdateData( return input, nil } +func tagSetList2Conditions(tagsetList tagutils.TTagSetList, keys []string, q *sqlchemy.SQuery) sqlchemy.ICondition { + for i := range keys { + if !strings.HasPrefix(keys[i], "org:") { + keys[i] = "org:" + keys[i] + } + } + conds := make([]sqlchemy.ICondition, 0) + paths := tagutils.TagSetList2Paths(tagsetList, keys) + for i := range paths { + label := api.JoinLabels(paths[i]...) + labelSlash := label + api.OrganizationLabelSeparator + conds = append(conds, sqlchemy.Contains(q.Field("full_label"), labelSlash)) + conds = append(conds, sqlchemy.Startswith(q.Field("full_label"), labelSlash)) + conds = append(conds, sqlchemy.Equals(q.Field("full_label"), label)) + } + if len(conds) > 0 { + return sqlchemy.OR(conds...) + } + return nil +} + // 项目列表 func (manager *SOrganizationNodeManager) ListItemFilter( ctx context.Context, @@ -300,7 +322,27 @@ func (manager *SOrganizationNodeManager) ListItemFilter( return nil, errors.Wrapf(err, "FetchByIdOrName %s", query.OrgId) } } - q = q.Equals("org_id", orgObj.GetId()) + org := orgObj.(*SOrganization) + q = q.Equals("org_id", org.GetId()) + + var cond sqlchemy.ICondition + switch org.Type { + case api.OrgTypeDomain: + if !query.PolicyDomainTags.IsEmpty() { + cond = tagSetList2Conditions(query.PolicyDomainTags, org.GetKeys(), q) + } + case api.OrgTypeProject: + if !query.PolicyProjectTags.IsEmpty() { + cond = tagSetList2Conditions(query.PolicyProjectTags, org.GetKeys(), q) + } + case api.OrgTypeObject: + if !query.PolicyObjectTags.IsEmpty() { + cond = tagSetList2Conditions(query.PolicyObjectTags, org.GetKeys(), q) + } + } + if cond != nil { + q = q.Filter(cond) + } } if len(query.OrgType) > 0 { diff --git a/pkg/keystone/models/projects.go b/pkg/keystone/models/projects.go index ec2ab90fcc..7cba2a0343 100644 --- a/pkg/keystone/models/projects.go +++ b/pkg/keystone/models/projects.go @@ -249,8 +249,6 @@ func (manager *SProjectManager) ListItemFilter( userCred mcclient.TokenCredential, query api.ProjectListInput, ) (*sqlchemy.SQuery, error) { - log.Debugf("ProjectManager ListItemFilter query %s", jsonutils.Marshal(query).String()) - var err error q, err = manager.SIdentityBaseResourceManager.ListItemFilter(ctx, q, userCred, query.IdentityBaseResourceListInput) diff --git a/pkg/util/tagutils/tagset.go b/pkg/util/tagutils/tagset.go index 32c2044c27..f2879c7339 100644 --- a/pkg/util/tagutils/tagset.go +++ b/pkg/util/tagutils/tagset.go @@ -247,3 +247,36 @@ func TagsetMap2MapString(oTags map[string]TTagSet) map[string]string { } return ret } + +func TagSet2Paths(tagSet TTagSet, keys []string) [][]string { + ret := make([][]string, 0) + tagMap := tagset2Map(tagSet) + for _, k := range keys { + if vs, ok := tagMap[k]; ok { + nret := make([][]string, 0) + for _, v := range vs { + if len(ret) > 0 { + for i := range ret { + cret := make([]string, len(ret[i]), len(ret[i])+1) + copy(cret, ret[i]) + cret = append(cret, v) + nret = append(nret, cret) + } + } else { + nret = append(nret, []string{v}) + } + } + ret = nret + } + } + return ret +} + +func TagSetList2Paths(tagsList TTagSetList, keys []string) [][]string { + ret := make([][]string, 0) + for i := range tagsList { + paths := TagSet2Paths(tagsList[i], keys) + ret = append(ret, paths...) + } + return ret +} diff --git a/pkg/util/tagutils/tagset_test.go b/pkg/util/tagutils/tagset_test.go index 570b416b37..3f42d99528 100644 --- a/pkg/util/tagutils/tagset_test.go +++ b/pkg/util/tagutils/tagset_test.go @@ -633,3 +633,177 @@ func TestTagSetKeyPrefix(t *testing.T) { } } } + +func TestTagSet2Paths(t *testing.T) { + cases := []struct { + tagset TTagSet + keys []string + paths [][]string + }{ + { + tagset: TTagSet{ + { + Key: "部门", + Value: "技术", + }, + { + Key: "环境", + Value: "测试", + }, + }, + keys: []string{ + "部门", "环境", + }, + paths: [][]string{ + { + "技术", "测试", + }, + }, + }, + { + tagset: TTagSet{ + { + Key: "部门", + Value: "技术", + }, + { + Key: "环境", + Value: "测试", + }, + { + Key: "环境", + Value: "研发", + }, + }, + keys: []string{ + "部门", "环境", + }, + paths: [][]string{ + { + "技术", "测试", + }, + { + "技术", "研发", + }, + }, + }, + { + tagset: TTagSet{ + { + Key: "部门", + Value: "技术", + }, + { + Key: "环境", + Value: "测试", + }, + { + Key: "业务", + Value: "TDCC", + }, + }, + keys: []string{ + "业务", "部门", "环境", + }, + paths: [][]string{ + { + "TDCC", "技术", "测试", + }, + }, + }, + { + tagset: TTagSet{ + { + Key: "部门", + Value: "技术", + }, + { + Key: "环境", + Value: "测试", + }, + { + Key: "业务", + Value: "TDCC", + }, + { + Key: "业务", + Value: "TKE", + }, + }, + keys: []string{ + "业务", "部门", "环境", + }, + paths: [][]string{ + { + "TDCC", "技术", "测试", + }, + { + "TKE", "技术", "测试", + }, + }, + }, + { + tagset: TTagSet{ + { + Key: "部门", + Value: "技术", + }, + { + Key: "环境", + Value: "测试", + }, + { + Key: "环境", + Value: "生产", + }, + { + Key: "业务", + Value: "TDCC", + }, + { + Key: "业务", + Value: "TKE", + }, + }, + keys: []string{ + "业务", "部门", "环境", + }, + paths: [][]string{ + { + "TDCC", "技术", "测试", + }, + { + "TKE", "技术", "测试", + }, + { + "TDCC", "技术", "生产", + }, + { + "TKE", "技术", "生产", + }, + }, + }, + { + tagset: TTagSet{ + { + Key: "org:系统", + Value: "G-BOOK", + }, + }, + keys: []string{ + "org:系统", "org:业务模块", "org:環境", + }, + paths: [][]string{ + { + "G-BOOK", + }, + }, + }, + } + for _, c := range cases { + paths := TagSet2Paths(c.tagset, c.keys) + if jsonutils.Marshal(paths).String() != jsonutils.Marshal(c.paths).String() { + t.Errorf("tagset: %s keys: %s want %s got %s", jsonutils.Marshal(c.tagset), jsonutils.Marshal(c.keys), jsonutils.Marshal(c.paths), jsonutils.Marshal(paths)) + } + } +}