Files
cloudpods/pkg/keystone/models/organization_nodes.go
T
Jian QiuandQiu Jian 29c76797f2 Hotfix/qj organization node label contains separator2 (#19609)
* fix: organization node label contains separator, round 2

* fix: hidden parent org nodes

---------

Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
2024-03-04 18:14:29 +08:00

522 lines
16 KiB
Go

// Copyright 2019 Yunion
//
// 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.
package models
import (
"context"
"crypto/sha256"
"database/sql"
"encoding/binary"
"fmt"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/rbacscope"
"yunion.io/x/pkg/utils"
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/identity"
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/util/stringutils2"
"yunion.io/x/onecloud/pkg/util/tagutils"
)
var _ db.IModelManager = (*SOrganizationNodeManager)(nil)
var _ db.IModel = (*SOrganizationNode)(nil)
type SOrganizationNodeManager struct {
db.SStandaloneResourceBaseManager
db.SPendingDeletedBaseManager
}
var OrganizationNodeManager *SOrganizationNodeManager
func init() {
OrganizationNodeManager = &SOrganizationNodeManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SOrganizationNode{},
"organization_nodes_tbl",
"organization_node",
"organization_nodes",
),
}
OrganizationNodeManager.SetVirtualObject(OrganizationNodeManager)
OrganizationNodeManager.TableSpec().AddIndex(true, "deleted", "org_id", "full_label", "level")
}
type SOrganizationNode struct {
db.SStandaloneResourceBase `name:""`
db.SPendingDeletedBase
OrgId string `width:"36" charset:"ascii" list:"user" create:"admin_required"`
FullLabel string `width:"180" charset:"utf8" list:"user" create:"admin_required"`
Level int `list:"user" create:"admin_required"`
Weight int `list:"user" create:"admin_required" update:"admin" default:"1"`
}
func (orgNode *SOrganizationNode) GetOrganization() (*SOrganization, error) {
return OrganizationManager.fetchOrganizationById(orgNode.OrgId)
}
func (orgNode *SOrganizationNode) GetChildCount() (int, error) {
q := OrganizationNodeManager.Query().Startswith("full_label", orgNode.FullLabel+api.OrganizationLabelSeparator)
cnt, err := q.CountWithError()
if err != nil {
return 0, errors.Wrap(err, "CountWithError")
}
return cnt, nil
}
func (orgNode *SOrganizationNode) GetDirectChildCount() (int, error) {
q := OrganizationNodeManager.Query().Startswith("full_label", orgNode.FullLabel+api.OrganizationLabelSeparator)
q = q.Equals("level", orgNode.Level+1)
cnt, err := q.CountWithError()
if err != nil {
return 0, errors.Wrap(err, "CountWithError")
}
return cnt, nil
}
func generateId(orgId string, fullLabel string, level int) string {
h := sha256.New()
h.Write([]byte(orgId))
h.Write([]byte(fullLabel))
bs := make([]byte, 4)
binary.LittleEndian.PutUint32(bs, uint32(level))
h.Write(bs)
return fmt.Sprintf("%x", h.Sum(nil))
}
func (manager *SOrganizationNodeManager) ensureNode(ctx context.Context, orgId string, fullLabel string, weight *int, desc string) (*SOrganizationNode, error) {
labels := api.SplitLabel(fullLabel)
level := len(labels)
label := labels[level-1]
id := generateId(orgId, fullLabel, level)
obj, err := manager.FetchById(id)
if err != nil {
if errors.Cause(err) != sql.ErrNoRows {
return nil, errors.Wrap(err, "FetchById")
}
// not exist
if weight == nil {
one := 1
weight = &one
}
} else {
// exist
if weight == nil {
weight = &obj.(*SOrganizationNode).Weight
}
if len(desc) == 0 {
desc = obj.(*SOrganizationNode).Description
}
}
node := &SOrganizationNode{
OrgId: orgId,
FullLabel: fullLabel,
Level: level,
Weight: *weight,
}
node.Description = desc
node.Name = label
node.Id = id
err = manager.TableSpec().InsertOrUpdate(ctx, node)
if err != nil {
return nil, errors.Wrap(err, "InsertOrUpdate")
}
return node, nil
}
func (orgNode *SOrganizationNode) PerformBind(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.OrganizationNodePerformBindInput,
) (jsonutils.JSONObject, error) {
orgNode.startOrganizationBindTask(ctx, userCred, input, true)
return nil, nil
}
func (orgNode *SOrganizationNode) PerformUnbind(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.OrganizationNodePerformBindInput,
) (jsonutils.JSONObject, error) {
orgNode.startOrganizationBindTask(ctx, userCred, input, false)
return nil, nil
}
func (orgNode *SOrganizationNode) startOrganizationBindTask(
ctx context.Context,
userCred mcclient.TokenCredential,
input api.OrganizationNodePerformBindInput,
bind bool,
) error {
params := jsonutils.NewDict()
params.Set("input", jsonutils.Marshal(input))
params.Set("bind", jsonutils.NewBool(bind))
task, err := taskman.TaskManager.NewTask(ctx, "OrganizationBindTask", orgNode, userCred, params, "", "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (orgNode *SOrganizationNode) BindTargetIds(ctx context.Context, userCred mcclient.TokenCredential, input api.OrganizationNodePerformBindInput, bind bool) error {
org, err := orgNode.GetOrganization()
if err != nil {
return errors.Wrap(err, "GetOrganization")
}
tags := orgNode.GetTags(org)
if !bind {
for k := range tags {
tags[k] = "None"
}
}
var errs []error
for _, id := range input.TargetId {
err := orgNode.bindTargetId(ctx, userCred, input.ResourceType, id, tags)
if err != nil {
errs = append(errs, err)
}
}
return errors.NewAggregate(errs)
}
func (orgNode *SOrganizationNode) bindTargetId(ctx context.Context, userCred mcclient.TokenCredential, resType string, idstr string, tags map[string]string) error {
org, err := OrganizationManager.fetchOrganizationById(orgNode.OrgId)
if err != nil {
return errors.Wrap(err, "fetchOrganizationById")
}
switch org.Type {
case api.OrgTypeProject:
return orgNode.bindProject(ctx, userCred, idstr, tags)
case api.OrgTypeDomain:
return orgNode.bindDomain(ctx, userCred, idstr, tags)
case api.OrgTypeObject:
return orgNode.bindObject(ctx, userCred, resType, idstr, tags)
}
return errors.Wrapf(errors.ErrNotSupported, "cannot bind to %s %s", resType, idstr)
}
func (orgNode *SOrganizationNode) bindProject(ctx context.Context, userCred mcclient.TokenCredential, idstr string, tags map[string]string) error {
projObj, err := ProjectManager.FetchById(idstr)
if err != nil {
return errors.Wrapf(err, "ProjectManager.FetchById %s", idstr)
}
return projObj.(*SProject).SetOrganizationMetadataAll(ctx, tags, userCred)
}
func (orgNode *SOrganizationNode) bindDomain(ctx context.Context, userCred mcclient.TokenCredential, idstr string, tags map[string]string) error {
domainObj, err := DomainManager.FetchById(idstr)
if err != nil {
return errors.Wrapf(err, "DomainManager.FetchById %s", idstr)
}
return domainObj.(*SDomain).SetOrganizationMetadataAll(ctx, tags, userCred)
}
func (orgNode *SOrganizationNode) bindObject(ctx context.Context, userCred mcclient.TokenCredential, resType, idstr string, tags map[string]string) error {
s := auth.GetSession(ctx, userCred, consts.GetRegion())
module, err := modulebase.GetModule(s, resType)
if err != nil {
return errors.Wrap(err, "GetModule")
}
input := jsonutils.Marshal(tags)
_, err = module.PerformAction(s, idstr, "set-org-metadata", input)
if err != nil {
return errors.Wrapf(err, "PerformAction set-org-metadata %s", idstr)
}
return nil
}
func (orgNode *SOrganizationNode) GetShortDesc(ctx context.Context) *jsonutils.JSONDict {
desc := orgNode.SStandaloneResourceBase.GetShortDesc(ctx)
desc.Set("level", jsonutils.NewInt(int64(orgNode.Level)))
desc.Set("full_label", jsonutils.NewString(orgNode.FullLabel))
return desc
}
func (orgNode *SOrganizationNode) ValidateUpdateData(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.OrganizationNodeUpdateInput,
) (api.OrganizationNodeUpdateInput, error) {
var err error
input.StandaloneResourceBaseUpdateInput, err = orgNode.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, input.StandaloneResourceBaseUpdateInput)
if err != nil {
return input, errors.Wrap(err, "SStandaloneResourceBase.ValidateUpdateData")
}
// not allow to update name
if len(input.Name) > 0 && input.Name != orgNode.Name {
return input, errors.Wrap(httperrors.ErrForbidden, "not allow to update name")
}
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 {
for j := range paths[i] {
label := api.JoinLabels(paths[i][:j+1]...)
conds = append(conds, sqlchemy.Equals(q.Field("full_label"), label))
if j == len(paths[i])-1 {
labelSlash := label + api.OrganizationLabelSeparator
conds = append(conds, sqlchemy.Startswith(q.Field("full_label"), labelSlash))
}
}
}
if len(conds) > 0 {
return sqlchemy.OR(conds...)
}
return nil
}
// 项目列表
func (manager *SOrganizationNodeManager) ListItemFilter(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
query api.OrganizationNodeListInput,
) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StandaloneResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.ListItemFilter")
}
if len(query.OrgId) > 0 {
orgObj, err := OrganizationManager.FetchByIdOrName(ctx, userCred, query.OrgId)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError2(OrganizationManager.Keyword(), query.OrgId)
} else {
return nil, errors.Wrapf(err, "FetchByIdOrName %s", query.OrgId)
}
}
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 {
orgSubQ := OrganizationManager.Query().SubQuery()
q = q.Join(orgSubQ, sqlchemy.Equals(q.Field("org_id"), orgSubQ.Field("id")))
q = q.Filter(sqlchemy.Equals(orgSubQ.Field("type"), query.OrgType))
}
if query.Level != 0 {
q = q.Equals("level", query.Level)
}
return q, nil
}
func (manager *SOrganizationNodeManager) OrderByExtraFields(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
query api.OrganizationNodeListInput,
) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StandaloneResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.OrderByExtraFields")
}
return q, nil
}
func (manager *SOrganizationNodeManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SStandaloneResourceBaseManager.QueryDistinctExtraField(q, field)
if err == nil {
return q, nil
}
return q, httperrors.ErrNotFound
}
func (orgNode *SOrganizationNode) GetTags(org *SOrganization) map[string]string {
tags := make(map[string]string)
keys := api.SplitLabel(org.Keys)
values := api.SplitLabel(orgNode.FullLabel)
for i := 0; i < orgNode.Level; i++ {
k := db.ORGANIZATION_TAG_PREFIX + keys[i]
v := values[i]
tags[k] = v
}
return tags
}
func (orgNode *SOrganizationNode) GetTagSet(org *SOrganization) tagutils.TTagSet {
tags := make(tagutils.TTagSet, 0)
keys := api.SplitLabel(org.Keys)
values := api.SplitLabel(orgNode.FullLabel)
for i := 0; i < orgNode.Level; i++ {
tag := tagutils.STag{
Key: db.ORGANIZATION_TAG_PREFIX + keys[i],
Value: values[i],
}
tags = append(tags, tag)
}
return tags
}
func (manager *SOrganizationNodeManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []api.SOrganizationNodeDetails {
rows := make([]api.SOrganizationNodeDetails, len(objs))
orgIds := make([]string, 0)
for i := range rows {
orgNode := objs[i].(*SOrganizationNode)
if !utils.IsInArray(orgNode.OrgId, orgIds) {
orgIds = append(orgIds, orgNode.OrgId)
}
}
orgMaps := make(map[string]SOrganization)
err := db.FetchModelObjectsByIds(OrganizationManager, "id", orgIds, &orgMaps)
if err != nil {
log.Errorf("FetchModelObjectsByIds fail %s", err)
return rows
}
stdRows := manager.SStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
for i := range rows {
orgNode := objs[i].(*SOrganizationNode)
if org, ok := orgMaps[orgNode.OrgId]; ok {
rows[i] = api.SOrganizationNodeDetails{
SOrganizationNode: api.SOrganizationNode{
OrgId: orgNode.OrgId,
FullLabel: orgNode.FullLabel,
Level: orgNode.Level,
Weight: orgNode.Weight,
},
StandaloneResourceDetails: stdRows[i],
Organization: org.Name,
Tags: orgNode.GetTagSet(&org),
Type: org.Type,
}
rows[i].Id = orgNode.Id
}
}
return rows
}
func (manager *SOrganizationNodeManager) FilterBySystemAttributes(q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
q = manager.SStandaloneResourceBaseManager.FilterBySystemAttributes(q, userCred, query, scope)
q = manager.SPendingDeletedBaseManager.FilterBySystemAttributes(manager.GetIStandaloneModelManager(), q, userCred, query, scope)
return q
}
func (orgNode *SOrganizationNode) ValidateDeleteCondition(ctx context.Context, info *api.ProjectDetails) error {
childCnt, err := orgNode.GetDirectChildCount()
if err != nil {
return errors.Wrap(err, "GetDirectChildCount")
}
if childCnt > 0 {
return errors.Wrapf(httperrors.ErrNotEmpty, "childnodes %d", childCnt)
}
return orgNode.SStandaloneResourceBase.ValidateDeleteCondition(ctx, nil)
}
// fake delete
func (orgNode *SOrganizationNode) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
if !orgNode.PendingDeleted {
err := orgNode.SPendingDeletedBase.MarkPendingDelete(orgNode.GetIStandaloneModel(), ctx, userCred, "")
if err != nil {
return errors.Wrap(err, "MarkPendingDelete")
}
}
return nil
}
func (manager *SOrganizationNodeManager) fetchOrgNodesInfo(ctx context.Context, userCred mcclient.TokenCredential, nodeIds []string, isList bool) ([]api.SOrganizationNodeInfo, error) {
q := manager.Query().In("id", nodeIds)
nodes := make([]SOrganizationNode, 0)
err := db.FetchModelObjects(manager, q, &nodes)
if err != nil {
return nil, errors.Wrap(err, "FetchModelObjects")
}
infs := make([]interface{}, 0)
for i := range nodes {
infs = append(infs, &nodes[i])
}
nodeDetails := manager.FetchCustomizeColumns(ctx, userCred, nil, infs, nil, isList)
ret := make([]api.SOrganizationNodeInfo, 0)
for i := range nodeDetails {
node := nodeDetails[i]
ret = append(ret, api.SOrganizationNodeInfo{
Id: node.Id,
FullLabel: node.FullLabel,
OrgId: node.OrgId,
Organization: node.Organization,
Tags: node.Tags,
Type: node.Type,
})
}
return ret, nil
}