tag filter for rbac

This commit is contained in:
Qiu Jian
2021-12-01 10:06:02 +08:00
parent ae7c972afc
commit fbe741f6cd
21 changed files with 276 additions and 155 deletions
+1 -2
View File
@@ -21,8 +21,6 @@ import (
"strings"
"time"
"yunion.io/x/onecloud/pkg/util/tagutils"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
@@ -38,6 +36,7 @@ import (
"yunion.io/x/onecloud/pkg/util/fileutils2"
"yunion.io/x/onecloud/pkg/util/rbacutils"
"yunion.io/x/onecloud/pkg/util/shellutils"
"yunion.io/x/onecloud/pkg/util/tagutils"
)
func createPolicy(s *mcclient.ClientSession, name string, policy string, domain string, enabled bool, disabled bool, desc string, scope string, isSystem *bool, objectags, projecttags, domaintags tagutils.TTagSet) error {
+7 -1
View File
@@ -16,11 +16,11 @@ package identity
import (
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/util/tagutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/apis"
"yunion.io/x/onecloud/pkg/util/rbacutils"
"yunion.io/x/onecloud/pkg/util/tagutils"
)
type IdentityBaseResourceCreateInput struct {
@@ -159,6 +159,9 @@ type ProjectListInput struct {
// 过滤出指定用户或者组可以加入的项目
Jointable *bool `json:"jointable"`
// project tags filter imposed by policy
PolicyProjectTags tagutils.TTagSetList `json:"policy_project_tags"`
}
type DomainListInput struct {
@@ -171,6 +174,9 @@ type DomainListInput struct {
// 按IDP_ENTITY_ID过滤
IdpEntityId string `json:"idp_entity_id"`
// domain tags filter imposed by policy
PolicyDomainTags tagutils.TTagSetList `json:"policy_domain_tags"`
}
type UserListInput struct {
+23 -38
View File
@@ -1301,11 +1301,11 @@ func (dispatcher *DBModelDispatcher) Create(ctx context.Context, query jsonutils
}
}
var requireTags tagutils.TTagSet
var policyResult rbacutils.SPolicyResult
if consts.IsRbacEnabled() {
requireTags, err = isClassRbacAllowed(ctx, dispatcher.modelManager, userCred, ownerId, policy.PolicyActionCreate)
policyResult, err = isClassRbacAllowed(ctx, dispatcher.modelManager, userCred, ownerId, policy.PolicyActionCreate)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "isClassRbacAllowed")
}
}
@@ -1313,12 +1313,7 @@ func (dispatcher *DBModelDispatcher) Create(ctx context.Context, query jsonutils
ctx = InitPendingUsagesInContext(ctx)
}
if len(requireTags) > 0 {
// add requireTags to require data
for _, tag := range requireTags {
data.(*jsonutils.JSONDict).Add(jsonutils.NewString(tag.Value), "__meta__", tag.Key)
}
}
data.(*jsonutils.JSONDict).Update(policyResult.Json())
model, err := DoCreate(dispatcher.modelManager, ctx, userCred, query, data, ownerId)
if err != nil {
@@ -1395,19 +1390,15 @@ func (dispatcher *DBModelDispatcher) BatchCreate(ctx context.Context, query json
}
}
var requireTags tagutils.TTagSet
var policyResult rbacutils.SPolicyResult
if consts.IsRbacEnabled() {
requireTags, err = isClassRbacAllowed(ctx, manager, userCred, ownerId, policy.PolicyActionCreate)
policyResult, err = isClassRbacAllowed(ctx, manager, userCred, ownerId, policy.PolicyActionCreate)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "isClassRbacAllowd")
}
}
if len(requireTags) > 0 {
for _, tag := range requireTags {
data.(*jsonutils.JSONDict).Add(jsonutils.NewString(tag.Value), "__meta__", tag.Key)
}
}
data.(*jsonutils.JSONDict).Update(policyResult.Json())
type sCreateResult struct {
model IModel
@@ -1608,6 +1599,7 @@ func reflectDispatcherInternal(
params = []interface{}{ctx, userCred, query, data}
}
var result rbacutils.SPolicyResult
if consts.IsRbacEnabled() {
if model == nil {
ownerId, err := fetchOwnerId(ctx, dispatcher.modelManager, userCred, data)
@@ -1619,30 +1611,12 @@ func reflectDispatcherInternal(
return nil, err
}
} else {
err := isObjectRbacAllowed(ctx, model, userCred, operator, spec)
var err error
result, err = isObjectRbacAllowedResult(ctx, model, userCred, operator, spec)
if err != nil {
return nil, err
}
}
} else {
allowFuncName := "Allow" + funcName
allowFuncValue := modelValue.MethodByName(allowFuncName)
if !allowFuncValue.IsValid() || allowFuncValue.IsNil() {
return nil, httperrors.NewActionNotFoundError("%s allow %s %s not found",
dispatcher.Keyword(), operator, spec)
}
outs, err := callFunc(allowFuncValue, allowFuncName, params...)
if err != nil {
return nil, err
}
if len(outs) != 1 {
return nil, httperrors.NewInternalServerError("Invald %s return value", allowFuncName)
}
if !outs[0].Bool() {
return nil, httperrors.NewForbiddenError("%s not allow to %s %s", dispatcher.Keyword(), operator, spec)
}
}
outs, err := callFunc(funcValue, funcName, params...)
@@ -1657,6 +1631,16 @@ func reflectDispatcherInternal(
if !gotypes.IsNil(errVal) {
return nil, errVal.(error)
} else {
if model != nil {
if _, ok := model.(IStandaloneModel); ok {
Metadata.rawSetValues(ctx, model.Keyword(), model.GetId(), tagutils.Tagset2MapString(result.ObjectTags.Flattern()), false, "")
if model.Keyword() == "project" {
Metadata.rawSetValues(ctx, model.Keyword(), model.GetId(), tagutils.Tagset2MapString(result.ProjectTags.Flattern()), false, "")
} else if model.Keyword() == "domain" {
Metadata.rawSetValues(ctx, model.Keyword(), model.GetId(), tagutils.Tagset2MapString(result.DomainTags.Flattern()), false, "")
}
}
}
if gotypes.IsNil(resVal.Interface()) {
return nil, nil
} else {
@@ -1718,10 +1702,11 @@ func (dispatcher *DBModelDispatcher) Update(ctx context.Context, idStr string, q
}
if consts.IsRbacEnabled() {
err := isObjectRbacAllowed(ctx, model, userCred, policy.PolicyActionUpdate)
result, err := isObjectRbacAllowedResult(ctx, model, userCred, policy.PolicyActionUpdate)
if err != nil {
return nil, err
}
data.(*jsonutils.JSONDict).Update(result.Json())
} else if !model.AllowUpdateItem(ctx, userCred) {
return nil, httperrors.NewForbiddenError("Not allow to update item")
}
+3 -6
View File
@@ -117,18 +117,15 @@ func (manager *SDomainizedResourceBaseManager) ListItemFilter(
q = q.In("domain_id", subq)
}
if len(query.DomainTags) > 0 {
meta := SMetadataResourceBaseModelManager{}
subq := meta.objIdQueryWithTags("domain", nil, query.DomainTags).SubQuery()
subq := objIdQueryWithTags("domain", nil, query.DomainTags).SubQuery()
q = q.In("domain_id", subq)
}
if len(query.NoDomainTags) > 0 {
meta := SMetadataResourceBaseModelManager{}
subq := meta.objIdQueryWithTags("domain", nil, query.NoDomainTags).SubQuery()
subq := objIdQueryWithTags("domain", nil, query.NoDomainTags).SubQuery()
q = q.NotIn("domain_id", subq)
}
if len(query.PolicyDomainTags) > 0 {
meta := SMetadataResourceBaseModelManager{}
subq := meta.objIdQueryWithTags("domain", nil, query.PolicyDomainTags).SubQuery()
subq := objIdQueryWithTags("domain", nil, query.PolicyDomainTags).SubQuery()
q = q.In("domain_id", subq)
}
return q, nil
+9 -6
View File
@@ -17,8 +17,6 @@ package db
import (
"strings"
"yunion.io/x/onecloud/pkg/util/tagutils"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/sqlchemy"
@@ -27,11 +25,16 @@ import (
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/stringutils2"
"yunion.io/x/onecloud/pkg/util/tagutils"
)
type SMetadataResourceBaseModelManager struct{}
func (meta *SMetadataResourceBaseModelManager) objIdQueryWithTags(modelName string, oTags tagutils.TTagSet, oMoreTags tagutils.TTagSetList) *sqlchemy.SQuery {
func ObjIdQueryWithTags(modelName string, oMoreTags tagutils.TTagSetList) *sqlchemy.SQuery {
return objIdQueryWithTags(modelName, nil, oMoreTags)
}
func objIdQueryWithTags(modelName string, oTags tagutils.TTagSet, oMoreTags tagutils.TTagSetList) *sqlchemy.SQuery {
metadataResQ := Metadata.Query().Equals("obj_type", modelName).SubQuery()
queries := make([]sqlchemy.IQuery, 0)
@@ -82,7 +85,7 @@ func (meta *SMetadataResourceBaseModelManager) ListItemFilter(
input apis.MetadataResourceListInput,
) *sqlchemy.SQuery {
if len(input.Tags) > 0 || len(input.ObjTags) > 0 {
sq := meta.objIdQueryWithTags(manager.Keyword(), input.Tags, input.ObjTags)
sq := objIdQueryWithTags(manager.Keyword(), input.Tags, input.ObjTags)
if sq != nil {
sqq := sq.SubQuery()
q = q.Join(sqq, sqlchemy.Equals(q.Field("id"), sqq.Field("obj_id")))
@@ -91,7 +94,7 @@ func (meta *SMetadataResourceBaseModelManager) ListItemFilter(
}
if len(input.PolicyObjectTags) > 0 {
sq := meta.objIdQueryWithTags(manager.Keyword(), nil, input.PolicyObjectTags)
sq := objIdQueryWithTags(manager.Keyword(), nil, input.PolicyObjectTags)
if sq != nil {
sqq := sq.SubQuery()
q = q.Join(sqq, sqlchemy.Equals(q.Field("id"), sqq.Field("obj_id")))
@@ -99,7 +102,7 @@ func (meta *SMetadataResourceBaseModelManager) ListItemFilter(
}
if len(input.NoTags) > 0 || len(input.NoObjTags) > 0 {
sq := meta.objIdQueryWithTags(manager.Keyword(), input.NoTags, input.NoObjTags)
sq := objIdQueryWithTags(manager.Keyword(), input.NoTags, input.NoObjTags)
if sq != nil {
q = q.Filter(sqlchemy.NotIn(q.Field("id"), sq.SubQuery()))
}
+21 -20
View File
@@ -19,7 +19,6 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
@@ -27,18 +26,26 @@ import (
"yunion.io/x/onecloud/pkg/util/tagutils"
)
func objectConfirmPolicyTags(ctx context.Context, userCred mcclient.TokenCredential, model IModel, result rbacutils.SPolicyResult) error {
func objectConfirmPolicyTags(ctx context.Context, model IModel, result rbacutils.SPolicyResult) error {
if _, ok := model.(IStandaloneModel); !ok {
// a plain resource
return nil
}
// now, its is a system resource
resTags, err := model.(IStandaloneModel).GetAllMetadata(userCred)
resTagsMap, err := Metadata.rawGetAll(model.Keyword(), model.GetId(), nil, "")
if err != nil {
return errors.Wrap(err, "Standalone model GetAllMetadata")
}
if !result.ObjectTags.Contains(tagutils.Map2Tagset(resTags)) {
return httperrors.NewNotSufficientPrivilegeError("resource tags not match (%s,require:%s)", jsonutils.Marshal(resTags), result.ObjectTags)
resTags := tagutils.Map2Tagset(resTagsMap)
if model.Keyword() == "domain" && !result.DomainTags.Contains(resTags) {
return httperrors.NewNotSufficientPrivilegeError("resource (domain) tags not match (tags:%s,require:%s)", jsonutils.Marshal(resTags), jsonutils.Marshal(result.DomainTags))
}
if model.Keyword() == "project" && !result.ProjectTags.Contains(resTags) {
return httperrors.NewNotSufficientPrivilegeError("resource (project) tags not match (tags:%s,require:%s)", jsonutils.Marshal(resTags), jsonutils.Marshal(result.ProjectTags))
}
if !result.ObjectTags.Contains(resTags) {
return httperrors.NewNotSufficientPrivilegeError("resource tags not match (tags:%s,require:%s)", jsonutils.Marshal(resTags), jsonutils.Marshal(result.ObjectTags))
}
if _, ok := model.(IDomainLevelModel); !ok {
// a system level resource
@@ -71,44 +78,38 @@ func objectConfirmPolicyTags(ctx context.Context, userCred mcclient.TokenCredent
return nil
}
func classConfirmPolicyTags(ctx context.Context, userCred mcclient.TokenCredential, manager IModelManager, objectOwnerId mcclient.IIdentityProvider, result rbacutils.SPolicyResult) (tagutils.TTagSet, error) {
func classConfirmPolicyTags(ctx context.Context, manager IModelManager, objectOwnerId mcclient.IIdentityProvider, result rbacutils.SPolicyResult) error {
if _, ok := manager.(IStandaloneModelManager); !ok {
return nil, nil
return nil
}
// now, the manager is a standalone model manager
requireResourceTags := result.ObjectTags.Flattern()
if _, ok := manager.(IDomainLevelModelManager); !ok {
// a system level resource manager
return requireResourceTags, nil
return nil
}
// now the manager is a domain level manager, we should check domain tags
if objectOwnerId != nil && objectOwnerId.GetProjectDomainId() != "" {
domain, err := TenantCacheManager.FetchDomainById(ctx, objectOwnerId.GetProjectDomainId())
if err != nil {
return nil, errors.Wrap(err, "TenantCacheManager.FetchDomainById")
return errors.Wrap(err, "TenantCacheManager.FetchDomainById")
}
if !result.DomainTags.Contains(domain.GetTags()) {
return nil, httperrors.NewNotSufficientPrivilegeError("domain tags not match (%s,require:%s)", jsonutils.Marshal(domain.GetTags()), result.DomainTags)
return httperrors.NewNotSufficientPrivilegeError("domain tags not match (%s,require:%s)", jsonutils.Marshal(domain.GetTags()), result.DomainTags)
}
}
if _, ok := manager.(IVirtualModelManager); !ok {
// a domain level resource manager
return requireResourceTags, nil
return nil
}
// now the manager is project level manager, we should check project tags
if objectOwnerId != nil && objectOwnerId.GetProjectId() != "" {
project, err := TenantCacheManager.FetchTenantById(ctx, objectOwnerId.GetProjectId())
if err != nil {
return nil, errors.Wrap(err, "TenantCacheManager.FetchTenantById")
return errors.Wrap(err, "TenantCacheManager.FetchTenantById")
}
if !result.ProjectTags.Contains(project.GetTags()) {
return nil, httperrors.NewNotSufficientPrivilegeError("project tags not match (%s,require:%s)", jsonutils.Marshal(project.GetTags()), result.ProjectTags)
return httperrors.NewNotSufficientPrivilegeError("project tags not match (%s,require:%s)", jsonutils.Marshal(project.GetTags()), result.ProjectTags)
}
}
return requireResourceTags, nil
}
func filterByTagFilters(q *sqlchemy.SQuery, result rbacutils.SPolicyResult) *sqlchemy.SQuery {
// to do
return q
return nil
}
+6 -4
View File
@@ -111,15 +111,17 @@ func (manager *SProjectizedResourceBaseManager) ListItemFilter(
q = q.In("tenant_id", subq)
}
if len(query.ProjectTags) > 0 {
meta := SMetadataResourceBaseModelManager{}
subq := meta.objIdQueryWithTags("project", nil, query.ProjectTags).SubQuery()
subq := objIdQueryWithTags("project", nil, query.ProjectTags).SubQuery()
q = q.In("tenant_id", subq)
}
if len(query.NoProjectTags) > 0 {
meta := SMetadataResourceBaseModelManager{}
subq := meta.objIdQueryWithTags("project", nil, query.NoProjectTags).SubQuery()
subq := objIdQueryWithTags("project", nil, query.NoProjectTags).SubQuery()
q = q.NotIn("tenant_id", subq)
}
if len(query.PolicyProjectTags) > 0 {
subq := objIdQueryWithTags("project", nil, query.PolicyProjectTags).SubQuery()
q = q.In("tenant_id", subq)
}
return q, nil
}
+27 -14
View File
@@ -18,13 +18,13 @@ import (
"context"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/rbacutils"
"yunion.io/x/onecloud/pkg/util/tagutils"
)
func IsObjectRbacAllowed(ctx context.Context, model IModel, userCred mcclient.TokenCredential, action string, extra ...string) error {
@@ -32,6 +32,11 @@ func IsObjectRbacAllowed(ctx context.Context, model IModel, userCred mcclient.To
}
func isObjectRbacAllowed(ctx context.Context, model IModel, userCred mcclient.TokenCredential, action string, extra ...string) error {
_, err := isObjectRbacAllowedResult(ctx, model, userCred, action, extra...)
return err
}
func isObjectRbacAllowedResult(ctx context.Context, model IModel, userCred mcclient.TokenCredential, action string, extra ...string) (rbacutils.SPolicyResult, error) {
manager := model.GetModelManager()
objOwnerId := model.GetOwnerId()
@@ -74,10 +79,14 @@ func isObjectRbacAllowed(ctx context.Context, model IModel, userCred mcclient.To
scope, result := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), manager.KeywordPlural(), action, extra...)
if !requireScope.HigherThan(scope) {
return objectConfirmPolicyTags(ctx, userCred, model, result)
if result.Result.IsAllow() && !requireScope.HigherThan(scope) {
err := objectConfirmPolicyTags(ctx, model, result)
if err != nil {
return rbacutils.PolicyDeny, errors.Wrap(err, "objectConfirmPolicyTags")
}
return result, nil
}
return httperrors.NewForbiddenError("not enough privilege (require:%s,allow:%s:resource:%s) [tags:%s]", requireScope, scope, resScope, result.String())
return rbacutils.PolicyDeny, httperrors.NewForbiddenError("not enough privilege (require:%s,allow:%s:resource:%s) [tags:%s]", requireScope, scope, resScope, result.String())
}
func isJointObjectRbacAllowed(ctx context.Context, item IJointModel, userCred mcclient.TokenCredential, action string, extra ...string) error {
@@ -89,7 +98,7 @@ func isJointObjectRbacAllowed(ctx context.Context, item IJointModel, userCred mc
return err1
}
func isClassRbacAllowed(ctx context.Context, manager IModelManager, userCred mcclient.TokenCredential, objOwnerId mcclient.IIdentityProvider, action string, extra ...string) (tagutils.TTagSet, error) {
func isClassRbacAllowed(ctx context.Context, manager IModelManager, userCred mcclient.TokenCredential, objOwnerId mcclient.IIdentityProvider, action string, extra ...string) (rbacutils.SPolicyResult, error) {
var ownerId mcclient.IIdentityProvider
if userCred != nil {
ownerId = userCred
@@ -128,10 +137,14 @@ func isClassRbacAllowed(ctx context.Context, manager IModelManager, userCred mcc
allowScope, result := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), manager.KeywordPlural(), action, extra...)
if !requireScope.HigherThan(allowScope) {
return classConfirmPolicyTags(ctx, userCred, manager, objOwnerId, result)
if result.Result.IsAllow() && !requireScope.HigherThan(allowScope) {
err := classConfirmPolicyTags(ctx, manager, objOwnerId, result)
if err != nil {
return rbacutils.PolicyDeny, errors.Wrap(err, "classConfirmPolicyTags")
}
return result, nil
}
return nil, httperrors.NewForbiddenError("not enough privilege (require:%s,allow:%s)", requireScope, allowScope)
return rbacutils.PolicyDeny, httperrors.NewForbiddenError("not enough privilege (require:%s,allow:%s)", requireScope, allowScope)
}
type IResource interface {
@@ -200,7 +213,7 @@ func IsAllowGet(ctx context.Context, scope rbacutils.TRbacScope, userCred mcclie
return false
}
result := userCred.IsAllow(scope, consts.GetServiceType(), obj.KeywordPlural(), policy.PolicyActionGet)
err := objectConfirmPolicyTags(ctx, userCred, obj, result)
err := objectConfirmPolicyTags(ctx, obj, result)
if err != nil {
log.Errorf("IsAllowGet %s", err)
return false
@@ -226,7 +239,7 @@ func IsAllowGetSpec(ctx context.Context, scope rbacutils.TRbacScope, userCred mc
return false
}
result := userCred.IsAllow(scope, consts.GetServiceType(), obj.KeywordPlural(), policy.PolicyActionGet, spec)
err := objectConfirmPolicyTags(ctx, userCred, obj, result)
err := objectConfirmPolicyTags(ctx, obj, result)
if err != nil {
log.Errorf("IsAllowGetSpec %s", err)
return false
@@ -252,7 +265,7 @@ func IsAllowPerform(ctx context.Context, scope rbacutils.TRbacScope, userCred mc
return false
}
result := userCred.IsAllow(scope, consts.GetServiceType(), obj.KeywordPlural(), policy.PolicyActionPerform, action)
err := objectConfirmPolicyTags(ctx, userCred, obj, result)
err := objectConfirmPolicyTags(ctx, obj, result)
if err != nil {
log.Errorf("IsAllowPerform %s", err)
return false
@@ -278,7 +291,7 @@ func IsAllowUpdate(ctx context.Context, scope rbacutils.TRbacScope, userCred mcc
return false
}
result := userCred.IsAllow(scope, consts.GetServiceType(), obj.KeywordPlural(), policy.PolicyActionUpdate)
err := objectConfirmPolicyTags(ctx, userCred, obj, result)
err := objectConfirmPolicyTags(ctx, obj, result)
if err != nil {
log.Errorf("IsAllowUpdate %s", err)
return false
@@ -304,7 +317,7 @@ func IsAllowUpdateSpec(ctx context.Context, scope rbacutils.TRbacScope, userCred
return false
}
result := userCred.IsAllow(scope, consts.GetServiceType(), obj.KeywordPlural(), policy.PolicyActionUpdate, spec)
err := objectConfirmPolicyTags(ctx, userCred, obj, result)
err := objectConfirmPolicyTags(ctx, obj, result)
if err != nil {
log.Errorf("IsAllowUpdateSpec %s", err)
return false
@@ -330,7 +343,7 @@ func IsAllowDelete(ctx context.Context, scope rbacutils.TRbacScope, userCred mcc
return false
}
result := userCred.IsAllow(scope, consts.GetServiceType(), obj.KeywordPlural(), policy.PolicyActionDelete)
err := objectConfirmPolicyTags(ctx, userCred, obj, result)
err := objectConfirmPolicyTags(ctx, obj, result)
if err != nil {
log.Errorf("IsAllowDelete %s", err)
return false
+2 -2
View File
@@ -478,7 +478,7 @@ func SharablePerformPublic(model ISharableBaseModel, ctx context.Context, userCr
return errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require %s allow %s", requireScope, allowScope)
}
err = objectConfirmPolicyTags(ctx, userCred, model, policyTags)
err = objectConfirmPolicyTags(ctx, model, policyTags)
if err != nil {
return errors.Wrap(err, "objectConfirmPolicyTags")
}
@@ -524,7 +524,7 @@ func SharablePerformPrivate(model ISharableBaseModel, ctx context.Context, userC
return errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require %s allow %s", requireScope, allowScope)
}
err := objectConfirmPolicyTags(ctx, userCred, model, policyTags)
err := objectConfirmPolicyTags(ctx, model, policyTags)
if err != nil {
return errors.Wrap(err, "objectConfirmPolicyTags")
}
+1 -1
View File
@@ -193,7 +193,7 @@ func (manager *SSharedResourceManager) shareToTarget(
return nil, errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require %s allow %s", requireScope, allowScope)
}
err = objectConfirmPolicyTags(ctx, userCred, model, policyTags)
err = objectConfirmPolicyTags(ctx, model, policyTags)
if err != nil {
return nil, errors.Wrap(err, "objectConfirmPolicyTags")
}
+26
View File
@@ -19,6 +19,7 @@ import (
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/stringutils"
"yunion.io/x/sqlchemy"
@@ -30,6 +31,7 @@ import (
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/rbacutils"
"yunion.io/x/onecloud/pkg/util/stringutils2"
"yunion.io/x/onecloud/pkg/util/tagutils"
)
type UUIDGenerator func() string
@@ -397,6 +399,12 @@ func (model *SStandaloneAnonResourceBase) PerformSetUserMetadata(ctx context.Con
return nil, nil
}
type sPolicyTags struct {
PolicyObjectTags tagutils.TTagSetList `json:"policy_object_tags"`
PolicyProjectTags tagutils.TTagSetList `json:"policy_project_tags"`
PolicyDomainTags tagutils.TTagSetList `json:"policy_domain_tags"`
}
func (model *SStandaloneAnonResourceBase) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
model.SResourceBase.PostUpdate(ctx, userCred, query, data)
@@ -405,6 +413,8 @@ func (model *SStandaloneAnonResourceBase) PostUpdate(ctx context.Context, userCr
if err == nil {
model.PerformMetadata(ctx, userCred, nil, meta)
}
model.applyPolicyTags(ctx, userCred, data)
}
func (model *SStandaloneAnonResourceBase) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
@@ -415,6 +425,22 @@ func (model *SStandaloneAnonResourceBase) PostCreate(ctx context.Context, userCr
if err == nil {
model.PerformMetadata(ctx, userCred, nil, meta)
}
model.applyPolicyTags(ctx, userCred, data)
}
func (model *SStandaloneAnonResourceBase) applyPolicyTags(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject) {
tags := sPolicyTags{}
data.Unmarshal(&tags)
log.Debugf("applyPolicyTags: %s", jsonutils.Marshal(tags))
if len(tags.PolicyObjectTags) > 0 {
model.PerformMetadata(ctx, userCred, nil, tagutils.Tagset2MapString(tags.PolicyObjectTags.Flattern()))
}
if model.Keyword() == "project" && len(tags.PolicyProjectTags) > 0 {
model.PerformMetadata(ctx, userCred, nil, tagutils.Tagset2MapString(tags.PolicyProjectTags.Flattern()))
} else if model.Keyword() == "domain" && len(tags.PolicyDomainTags) > 0 {
model.PerformMetadata(ctx, userCred, nil, tagutils.Tagset2MapString(tags.PolicyDomainTags.Flattern()))
}
}
func (model *SStandaloneAnonResourceBase) PostDelete(ctx context.Context, userCred mcclient.TokenCredential) {
+1 -1
View File
@@ -407,7 +407,7 @@ func (model *SVirtualResourceBase) PerformChangeOwner(ctx context.Context, userC
return nil, errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require %s allow %s", requireScope, allowScope)
}
err = objectConfirmPolicyTags(ctx, userCred, model, policyTags)
err = objectConfirmPolicyTags(ctx, model, policyTags)
if err != nil {
return nil, errors.Wrap(err, "objectConfirmPolicyTags")
}
+6
View File
@@ -188,6 +188,12 @@ func (manager *SDomainManager) ListItemFilter(
}
q = q.NotEquals("id", api.KeystoneDomainRoot)
if len(query.PolicyDomainTags) > 0 {
// apply policy imposed domain tag filters
subq := db.ObjIdQueryWithTags("domain", query.PolicyDomainTags).SubQuery()
q = q.In("id", subq)
}
if query.Enabled != nil {
if *query.Enabled {
q = q.IsTrue("enabled")
+4 -5
View File
@@ -95,7 +95,7 @@ type SPolicy struct {
DomainTags tagutils.TTagSet `nullable:"true" list:"user" update:"admin" create:"admin_optional"`
// 匹配的资源标签
ResourceTags tagutils.TTagSet `nullable:"true" list:"user" update:"domain" create:"domain_optional"`
ObjectTags tagutils.TTagSet `nullable:"true" list:"user" update:"domain" create:"domain_optional"`
}
func (manager *SPolicyManager) InitializeData() error {
@@ -354,13 +354,13 @@ func (policy *SPolicy) ValidateUpdateData(ctx context.Context, userCred mcclient
case api.TAG_UPDATE_POLICY_REMOVE:
input.DomainTags = policy.DomainTags.Remove(input.DomainTags...)
input.ProjectTags = policy.ProjectTags.Remove(input.ProjectTags...)
input.ObjectTags = policy.ResourceTags.Remove(input.ObjectTags...)
input.ObjectTags = policy.ObjectTags.Remove(input.ObjectTags...)
case api.TAG_UPDATE_POLICY_REPLACE:
// do nothing
default:
input.DomainTags = policy.DomainTags.Append(input.DomainTags...)
input.ProjectTags = policy.ProjectTags.Append(input.ProjectTags...)
input.ObjectTags = policy.ResourceTags.Append(input.ObjectTags...)
input.ObjectTags = policy.ObjectTags.Append(input.ObjectTags...)
}
if input.Blob != nil {
@@ -403,7 +403,6 @@ func (policy *SPolicy) PostCreate(ctx context.Context, userCred mcclient.TokenCr
if err != nil {
log.Errorf("CancelPendingUsage fail %s", err)
}
// policyman.PolicyManager.SyncOnce()
}
@@ -598,7 +597,7 @@ func (policy *SPolicy) GetSharedDomains() []string {
}
func (policy *SPolicy) getPolicy() (*rbacutils.SPolicy, error) {
pc, err := rbacutils.DecodePolicyData(policy.DomainTags, policy.ProjectTags, policy.ResourceTags, policy.Blob)
pc, err := rbacutils.DecodePolicyData(policy.DomainTags, policy.ProjectTags, policy.ObjectTags, policy.Blob)
if err != nil {
return nil, errors.Wrap(err, "Decode")
}
+6
View File
@@ -221,6 +221,12 @@ func (manager *SProjectManager) ListItemFilter(
return nil, errors.Wrap(err, "SIdentityBaseResourceManager.ListItemFilter")
}
if len(query.PolicyProjectTags) > 0 {
// aplly policy imposed project tag filters
subq := db.ObjIdQueryWithTags("project", query.PolicyProjectTags).SubQuery()
q = q.In("id", subq)
}
userStr := query.UserId
if len(userStr) > 0 {
userObj, err := UserManager.FetchById(userStr)
+1 -2
View File
@@ -20,14 +20,13 @@ import (
"strings"
"time"
"yunion.io/x/onecloud/pkg/util/tagutils"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/gotypes"
"yunion.io/x/pkg/util/reflectutils"
dbapi "yunion.io/x/onecloud/pkg/apis/cloudcommon/db"
"yunion.io/x/onecloud/pkg/util/tagutils"
)
// Int returns a pointer to int type with the same value as the argument. This
+15
View File
@@ -0,0 +1,15 @@
// 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 tagutils // import "yunion.io/x/onecloud/pkg/util/tagutils"
+25 -45
View File
@@ -17,6 +17,8 @@ package tagutils
import (
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
type TTagSet []STag
@@ -76,54 +78,22 @@ func (ts TTagSet) Remove(ele ...STag) TTagSet {
return ts
}
func contains(v1, v2 []string) bool {
vv1 := stringutils2.SSortedStrings(v1)
vv2 := stringutils2.SSortedStrings(v2)
return stringutils2.Contains(vv1, vv2)
}
func (a TTagSet) Contains(b TTagSet) bool {
aNoB, _, _ := Split(a, b)
if len(aNoB) == 0 {
return true
} else {
return false
}
}
mapA := Tagset2Map(a)
mapB := Tagset2Map(b)
func (a TTagSet) Equals(b TTagSet) bool {
aNoB, _, bNoA := Split(a, b)
if len(aNoB) == 0 && len(bNoA) == 0 {
return true
} else {
return false
}
}
func Split(a, b TTagSet) (aNoB TTagSet, aAndB TTagSet, bNoA TTagSet) {
a_b := make([]STag, 0)
b_a := make([]STag, 0)
anb := make([]STag, 0)
i := 0
j := 0
for i < len(a) && j < len(b) {
switch Compare(a[i], b[j]) {
case 0:
anb = append(anb, a[i])
i += 1
j += 1
case -1:
a_b = append(a_b, a[i])
i += 1
case 1:
b_a = append(b_a, b[j])
j += 1
for k, v := range mapA {
if vs, ok := mapB[k]; !ok || !contains(v, vs) {
return false
}
}
if i < len(a) {
a_b = append(a_b, a[i:]...)
}
if j < len(b) {
b_a = append(b_a, b[j:]...)
}
aNoB = a_b
aAndB = anb
bNoA = b_a
return
return true
}
func Map2Tagset(meta map[string]string) TTagSet {
@@ -144,7 +114,17 @@ func Tagset2Map(oTags TTagSet) map[string][]string {
tags[tag.Key] = []string{}
}
if len(tag.Value) > 0 && !utils.IsInStringArray(tag.Value, tags[tag.Key]) {
tags[tag.Key] = append(tags[tag.Key], tag.Value)
tags[tag.Key] = stringutils2.SSortedStrings(tags[tag.Key]).Append(tag.Value)
}
}
return tags
}
func Tagset2MapString(oTags TTagSet) map[string]string {
tags := map[string]string{}
for _, tag := range oTags {
if _, ok := tags[tag.Key]; !ok {
tags[tag.Key] = tag.Value
}
}
return tags
+23
View File
@@ -46,6 +46,29 @@ func TestTTagSet_Contains(t *testing.T) {
},
contains: true,
},
{
t1: TTagSet{
STag{
Key: "project",
Value: "a",
},
STag{
Key: "project",
Value: "b",
},
},
t2: TTagSet{
STag{
Key: "project",
Value: "a",
},
STag{
Key: "env",
Value: "c",
},
},
contains: true,
},
{
t1: TTagSet{
STag{
+19 -7
View File
@@ -85,12 +85,24 @@ func (tsl TTagSetList) String() string {
return "[" + strings.Join(tss, ",") + "]"
}
func (tsl TTagSetList) Flattern() TTagSet {
ret := TTagSet{}
for _, ts := range tsl {
for k, v := range ts {
ret[k] = v
}
func (a TTagSetList) Len() int { return len(a) }
func (a TTagSetList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a TTagSetList) Less(i, j int) bool {
if len(a[i]) < len(a[j]) {
return true
} else if len(a[i]) > len(a[j]) {
return false
}
return ret
if a[i].Contains(a[j]) {
return true
}
return false
}
func (tsl TTagSetList) Flattern() TTagSet {
if len(tsl) == 0 {
return TTagSet{}
}
sort.Sort(tsl)
return tsl[len(tsl)-1]
}
+50 -1
View File
@@ -14,7 +14,12 @@
package tagutils
import "testing"
import (
"reflect"
"testing"
"yunion.io/x/jsonutils"
)
func TestTTagSetList_Contains(t *testing.T) {
cases := []struct {
@@ -243,3 +248,47 @@ func TestTTagSetList_Append(t *testing.T) {
}
}
}
func TestTTagSetList_Flattern(t *testing.T) {
cases := []struct {
tsl TTagSetList
want TTagSet
}{
{
tsl: TTagSetList{
TTagSet{
STag{
Key: "project",
Value: "a",
},
STag{
Key: "env",
Value: "product",
},
},
TTagSet{
STag{
Key: "project",
Value: "b",
},
},
},
want: TTagSet{
STag{
Key: "project",
Value: "a",
},
STag{
Key: "env",
Value: "product",
},
},
},
}
for _, c := range cases {
got := c.tsl.Flattern()
if !reflect.DeepEqual(got, c.want) {
t.Errorf("want %s got %s", jsonutils.Marshal(c.want), jsonutils.Marshal(got))
}
}
}