diff --git a/cmd/climc/shell/cloudaccounts.go b/cmd/climc/shell/cloudaccounts.go index c3fabce7cb..568bd14544 100644 --- a/cmd/climc/shell/cloudaccounts.go +++ b/cmd/climc/shell/cloudaccounts.go @@ -19,6 +19,7 @@ import ( "yunion.io/x/jsonutils" + api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/mcclient/options" @@ -653,4 +654,19 @@ func init() { printObject(result) return nil }) + + type CloudaccountShareModeOptions struct { + ID string `help:"ID or name of cloud account"` + MODE string `help:"cloud account share mode" choices:"account_domain|system|provider_domain"` + } + R(&CloudaccountShareModeOptions{}, "cloud-account-share-mode", "Set share_mode of a cloud account", func(s *mcclient.ClientSession, args *CloudaccountShareModeOptions) error { + input := api.CloudaccountShareModeInput{} + input.ShareMode = args.MODE + result, err := modules.Cloudaccounts.PerformAction(s, args.ID, "share-mode", jsonutils.Marshal(input)) + if err != nil { + return err + } + printObject(result) + return nil + }) } diff --git a/pkg/apis/compute/cloudaccount.go b/pkg/apis/compute/cloudaccount.go index 645ee862ed..3c04f83839 100644 --- a/pkg/apis/compute/cloudaccount.go +++ b/pkg/apis/compute/cloudaccount.go @@ -16,8 +16,10 @@ package compute import ( "yunion.io/x/jsonutils" + "yunion.io/x/pkg/utils" "yunion.io/x/onecloud/pkg/apis" + "yunion.io/x/onecloud/pkg/httperrors" ) type CloudaccountCredentialInput struct { @@ -66,3 +68,19 @@ type CloudaccountCreateInput struct { CloudaccountCredentialInput } + +type CloudaccountShareModeInput struct { + apis.Meta + + ShareMode string +} + +func (i CloudaccountShareModeInput) Validate() error { + if len(i.ShareMode) == 0 { + return httperrors.NewMissingParameterError("share_mode") + } + if !utils.IsInStringArray(i.ShareMode, CLOUD_ACCOUNT_SHARE_MODES) { + return httperrors.NewInputParameterError("invalid share_mode %s", i.ShareMode) + } + return nil +} diff --git a/pkg/apis/compute/cloudaccount_const.go b/pkg/apis/compute/cloudaccount_const.go index 7ade88bfe6..5e9a1a7285 100644 --- a/pkg/apis/compute/cloudaccount_const.go +++ b/pkg/apis/compute/cloudaccount_const.go @@ -92,3 +92,17 @@ const ( CLOUD_ENV_PRIVATE_ON_PREMISE = "private_or_onpremise" ) + +const ( + CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN = "account_domain" + CLOUD_ACCOUNT_SHARE_MODE_SYSTEM = "system" + CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN = "provider_domain" +) + +var ( + CLOUD_ACCOUNT_SHARE_MODES = []string{ + CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN, + CLOUD_ACCOUNT_SHARE_MODE_SYSTEM, + CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN, + } +) diff --git a/pkg/cloudcommon/db/db_dispatcher.go b/pkg/cloudcommon/db/db_dispatcher.go index 7ef5922d6f..0702f8a78d 100644 --- a/pkg/cloudcommon/db/db_dispatcher.go +++ b/pkg/cloudcommon/db/db_dispatcher.go @@ -794,16 +794,16 @@ func (dispatcher *DBModelDispatcher) Get(ctx context.Context, idStr string, quer } else if err != nil { return nil, err } - // log.Debugf("Get found %s", model) - var isAllow bool + if consts.IsRbacEnabled() { - isAllow = isObjectRbacAllowed(model, userCred, policy.PolicyActionGet) - } else { - isAllow = model.AllowGetDetails(ctx, userCred, query) - } - if !isAllow { + err := isObjectRbacAllowed(model, userCred, policy.PolicyActionGet) + if err != nil { + return nil, err + } + } else if !model.AllowGetDetails(ctx, userCred, query) { return nil, httperrors.NewForbiddenError("Not allow to get details") } + if userCred.HasSystemAdminPrivilege() && dispatcher.modelManager.GetSkipLog(ctx, userCred, query) { appParams := appsrv.AppContextGetParams(ctx) if appParams != nil { @@ -831,9 +831,11 @@ func (dispatcher *DBModelDispatcher) GetSpecific(ctx context.Context, idStr stri specCamel := utils.Kebab2Camel(spec, "-") modelValue := reflect.ValueOf(model) - var isAllow bool if consts.IsRbacEnabled() { - isAllow = isObjectRbacAllowed(model, userCred, policy.PolicyActionGet, spec) + err := isObjectRbacAllowed(model, userCred, policy.PolicyActionGet, spec) + if err != nil { + return nil, err + } } else { funcName := fmt.Sprintf("AllowGetDetails%s", specCamel) @@ -846,11 +848,9 @@ func (dispatcher *DBModelDispatcher) GetSpecific(ctx context.Context, idStr stri if len(outs) != 1 { return nil, httperrors.NewInternalServerError("Invald %s return value", funcName) } - isAllow = outs[0].Bool() - } - - if !isAllow { - return nil, httperrors.NewForbiddenError("%s not allow to get spec %s", dispatcher.Keyword(), spec) + if !outs[0].Bool() { + return nil, httperrors.NewForbiddenError("%s not allow to get spec %s", dispatcher.Keyword(), spec) + } } funcName := fmt.Sprintf("GetDetails%s", specCamel) @@ -1082,13 +1082,12 @@ func (dispatcher *DBModelDispatcher) Create(ctx context.Context, query jsonutils } } - var isAllow bool if consts.IsRbacEnabled() { - isAllow = isClassRbacAllowed(dispatcher.modelManager, userCred, ownerId, policy.PolicyActionCreate) - } else { - isAllow = dispatcher.modelManager.AllowCreateItem(ctx, userCred, query, data) - } - if !isAllow { + err := isClassRbacAllowed(dispatcher.modelManager, userCred, ownerId, policy.PolicyActionCreate) + if err != nil { + return nil, err + } + } else if !dispatcher.modelManager.AllowCreateItem(ctx, userCred, query, data) { return nil, httperrors.NewForbiddenError("Not allow to create item") } @@ -1152,13 +1151,12 @@ func (dispatcher *DBModelDispatcher) BatchCreate(ctx context.Context, query json } } - var isAllow bool if consts.IsRbacEnabled() { - isAllow = isClassRbacAllowed(manager, userCred, ownerId, policy.PolicyActionCreate) - } else { - isAllow = manager.AllowCreateItem(ctx, userCred, query, data) - } - if !isAllow { + err := isClassRbacAllowed(manager, userCred, ownerId, policy.PolicyActionCreate) + if err != nil { + return nil, err + } + } else if !manager.AllowCreateItem(ctx, userCred, query, data) { return nil, httperrors.NewForbiddenError("Not allow to create item") } @@ -1258,13 +1256,12 @@ func managerPerformCheckCreateData( } bodyDict := body.(*jsonutils.JSONDict) - var isAllow bool if consts.IsRbacEnabled() { - isAllow = isClassRbacAllowed(manager, userCred, ownerId, policy.PolicyActionPerform, action) - } else { - isAllow = manager.AllowPerformCheckCreateData(ctx, userCred, query, data) - } - if !isAllow { + err := isClassRbacAllowed(manager, userCred, ownerId, policy.PolicyActionPerform, action) + if err != nil { + return nil, err + } + } else if !manager.AllowPerformCheckCreateData(ctx, userCred, query, data) { return nil, httperrors.NewForbiddenError("not allow to perform %s", action) } @@ -1380,16 +1377,21 @@ func reflectDispatcherInternal( } } - var isAllow bool if consts.IsRbacEnabled() { if model == nil { ownerId, err := fetchOwnerId(ctx, dispatcher.modelManager, userCred, data) if err != nil { return nil, httperrors.NewGeneralError(err) } - isAllow = isClassRbacAllowed(dispatcher.modelManager, userCred, ownerId, operator, spec) + err = isClassRbacAllowed(dispatcher.modelManager, userCred, ownerId, operator, spec) + if err != nil { + return nil, err + } } else { - isAllow = isObjectRbacAllowed(model, userCred, operator, spec) + err := isObjectRbacAllowed(model, userCred, operator, spec) + if err != nil { + return nil, err + } } } else { allowFuncName := "Allow" + funcName @@ -1405,10 +1407,9 @@ func reflectDispatcherInternal( return nil, httperrors.NewInternalServerError("Invald %s return value", allowFuncName) } - isAllow = outs[0].Bool() - } - if !isAllow { - return nil, httperrors.NewForbiddenError("%s not allow to %s %s", dispatcher.Keyword(), operator, spec) + if !outs[0].Bool() { + return nil, httperrors.NewForbiddenError("%s not allow to %s %s", dispatcher.Keyword(), operator, spec) + } } outs := funcValue.Call(params) @@ -1495,13 +1496,12 @@ func (dispatcher *DBModelDispatcher) Update(ctx context.Context, idStr string, q return nil, httperrors.NewGeneralError(err) } - var isAllow bool if consts.IsRbacEnabled() { - isAllow = isObjectRbacAllowed(model, userCred, policy.PolicyActionUpdate) - } else { - isAllow = model.AllowUpdateItem(ctx, userCred) - } - if !isAllow { + err := isObjectRbacAllowed(model, userCred, policy.PolicyActionUpdate) + if err != nil { + return nil, err + } + } else if !model.AllowUpdateItem(ctx, userCred) { return nil, httperrors.NewForbiddenError("Not allow to update item") } @@ -1599,14 +1599,12 @@ func (dispatcher *DBModelDispatcher) Delete(ctx context.Context, idstr string, q } // log.Debugf("Delete %s", model.GetShortDesc(ctx)) - var isAllow bool if consts.IsRbacEnabled() { - isAllow = isObjectRbacAllowed(model, userCred, policy.PolicyActionDelete) - } else { - isAllow = model.AllowDeleteItem(ctx, userCred, query, data) - } - if !isAllow { - log.Errorf("not allow to delete") + err := isObjectRbacAllowed(model, userCred, policy.PolicyActionDelete) + if err != nil { + return nil, err + } + } else if !model.AllowDeleteItem(ctx, userCred, query, data) { return nil, httperrors.NewForbiddenError("%s(%s) not allow to delete", dispatcher.modelManager.KeywordPlural(), model.GetId()) } diff --git a/pkg/cloudcommon/db/db_joint_dispatcher.go b/pkg/cloudcommon/db/db_joint_dispatcher.go index d747d93281..f939c7f20d 100644 --- a/pkg/cloudcommon/db/db_joint_dispatcher.go +++ b/pkg/cloudcommon/db/db_joint_dispatcher.go @@ -144,27 +144,28 @@ func (dispatcher *DBJointModelDispatcher) Get(ctx context.Context, id1 string, i } else if err != nil { return nil, httperrors.NewGeneralError(err) } - var isAllow bool if consts.IsRbacEnabled() { - isAllow = isJointObjectRbacAllowed(item, userCred, policy.PolicyActionGet) - } else { - isAllow = item.AllowGetJointDetails(ctx, userCred, query, item) - } - if !isAllow { + err := isJointObjectRbacAllowed(item, userCred, policy.PolicyActionGet) + if err != nil { + return nil, err + } + } else if !item.AllowGetJointDetails(ctx, userCred, query, item) { return nil, httperrors.NewForbiddenError("Not allow to get details") } return getItemDetails(dispatcher.JointModelManager(), item, ctx, userCred, query) } func attachItems(dispatcher *DBJointModelDispatcher, master IStandaloneModel, slave IStandaloneModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - var isAllow bool if consts.IsRbacEnabled() { - isAllow = isObjectRbacAllowed(master, userCred, policy.PolicyActionPerform, "attach") && - isObjectRbacAllowed(slave, userCred, policy.PolicyActionPerform, "attach") - } else { - isAllow = dispatcher.JointModelManager().AllowAttach(ctx, userCred, master, slave) - } - if !isAllow { + err := isObjectRbacAllowed(master, userCred, policy.PolicyActionPerform, "attach") + if err != nil { + return nil, err + } + err = isObjectRbacAllowed(slave, userCred, policy.PolicyActionPerform, "attach") + if err != nil { + return nil, err + } + } else if !dispatcher.JointModelManager().AllowAttach(ctx, userCred, master, slave) { return nil, httperrors.NewForbiddenError("Not allow to attach") } // ownerProjId, err := fetchOwnerId(ctx, dispatcher.JointModelManager(), userCred, data) @@ -236,13 +237,12 @@ func (dispatcher *DBJointModelDispatcher) Update(ctx context.Context, id1 string return nil, httperrors.NewGeneralError(err) } - var isAllow bool if consts.IsRbacEnabled() { - isAllow = isJointObjectRbacAllowed(item, userCred, policy.PolicyActionUpdate) - } else { - isAllow = item.AllowUpdateJointItem(ctx, userCred, item) - } - if !isAllow { + err := isJointObjectRbacAllowed(item, userCred, policy.PolicyActionUpdate) + if err != nil { + return nil, err + } + } else if !item.AllowUpdateJointItem(ctx, userCred, item) { return nil, httperrors.NewForbiddenError("Not allow to update item") } @@ -260,14 +260,16 @@ func (dispatcher *DBJointModelDispatcher) Detach(ctx context.Context, id1 string return nil, httperrors.NewGeneralError(err) } - var isAllow bool if consts.IsRbacEnabled() { - isAllow = isObjectRbacAllowed(master, userCred, policy.PolicyActionPerform, "detach") && - isObjectRbacAllowed(slave, userCred, policy.PolicyActionPerform, "detach") - } else { - isAllow = item.AllowDetach(ctx, userCred, query, data) - } - if !isAllow { + err := isObjectRbacAllowed(master, userCred, policy.PolicyActionPerform, "detach") + if err != nil { + return nil, err + } + err = isObjectRbacAllowed(slave, userCred, policy.PolicyActionPerform, "detach") + if err != nil { + return nil, err + } + } else if !item.AllowDetach(ctx, userCred, query, data) { return nil, httperrors.NewForbiddenError("Not allow to attach") } diff --git a/pkg/cloudcommon/db/fetch.go b/pkg/cloudcommon/db/fetch.go index edfb531129..901e70cdc4 100644 --- a/pkg/cloudcommon/db/fetch.go +++ b/pkg/cloudcommon/db/fetch.go @@ -386,7 +386,7 @@ func FetchCheckQueryOwnerScope(ctx context.Context, userCred mcclient.TokenCrede requireScope = queryScope } if doCheckRbac && requireScope.HigherThan(allowScope) { - return nil, scope, httperrors.NewForbiddenError(fmt.Sprintf("not enough privilleges(require:%s,allow:%s,query:%s)", requireScope, allowScope, queryScope)) + return nil, scope, httperrors.NewForbiddenError(fmt.Sprintf("not enough privilege(require:%s,allow:%s,query:%s)", requireScope, allowScope, queryScope)) } return ownerId, queryScope, nil } diff --git a/pkg/cloudcommon/db/rbac.go b/pkg/cloudcommon/db/rbac.go index c5790ed620..2982aad641 100644 --- a/pkg/cloudcommon/db/rbac.go +++ b/pkg/cloudcommon/db/rbac.go @@ -15,17 +15,20 @@ package db import ( + "fmt" + "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" ) -func IsObjectRbacAllowed(model IModel, userCred mcclient.TokenCredential, action string, extra ...string) bool { +func IsObjectRbacAllowed(model IModel, userCred mcclient.TokenCredential, action string, extra ...string) error { return isObjectRbacAllowed(model, userCred, action, extra...) } -func isObjectRbacAllowed(model IModel, userCred mcclient.TokenCredential, action string, extra ...string) bool { +func isObjectRbacAllowed(model IModel, userCred mcclient.TokenCredential, action string, extra ...string) error { manager := model.GetModelManager() objOwnerId := model.GetOwnerId() @@ -65,17 +68,21 @@ func isObjectRbacAllowed(model IModel, userCred mcclient.TokenCredential, action scope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), manager.KeywordPlural(), action, extra...) if !requireScope.HigherThan(scope) { - return true + return nil } - - return false + return httperrors.NewForbiddenError(fmt.Sprintf("not enough privillege(require:%s,allow:%s)", requireScope, scope)) } -func isJointObjectRbacAllowed(item IJointModel, userCred mcclient.TokenCredential, action string, extra ...string) bool { - return isObjectRbacAllowed(item.Master(), userCred, action, extra...) || isObjectRbacAllowed(item.Slave(), userCred, action, extra...) +func isJointObjectRbacAllowed(item IJointModel, userCred mcclient.TokenCredential, action string, extra ...string) error { + err1 := isObjectRbacAllowed(item.Master(), userCred, action, extra...) + err2 := isObjectRbacAllowed(item.Slave(), userCred, action, extra...) + if err1 == nil || err2 == nil { + return nil + } + return err1 } -func isClassRbacAllowed(manager IModelManager, userCred mcclient.TokenCredential, objOwnerId mcclient.IIdentityProvider, action string, extra ...string) bool { +func isClassRbacAllowed(manager IModelManager, userCred mcclient.TokenCredential, objOwnerId mcclient.IIdentityProvider, action string, extra ...string) error { var ownerId mcclient.IIdentityProvider if userCred != nil { ownerId = userCred @@ -113,10 +120,9 @@ func isClassRbacAllowed(manager IModelManager, userCred mcclient.TokenCredential allowScope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), manager.KeywordPlural(), action, extra...) if !requireScope.HigherThan(allowScope) { - return true + return nil } - - return false + return httperrors.NewForbiddenError(fmt.Sprintf("not enough privilege(require:%s,allow:%s)", requireScope, allowScope)) } type IResource interface { diff --git a/pkg/cloudcommon/policy/defaults.go b/pkg/cloudcommon/policy/defaults.go index de84ea0595..2120c0a7c3 100644 --- a/pkg/cloudcommon/policy/defaults.go +++ b/pkg/cloudcommon/policy/defaults.go @@ -102,18 +102,6 @@ var ( Action: PolicyActionGet, Result: rbacutils.Allow, }, - { - Service: "compute", - Resource: "cloudproviders", - Action: PolicyActionList, - Result: rbacutils.Allow, - }, - { - Service: "compute", - Resource: "cloudproviders", - Action: PolicyActionGet, - Result: rbacutils.Allow, - }, { Service: "compute", Resource: "cachedimages", @@ -356,6 +344,18 @@ var ( Action: PolicyActionGet, Result: rbacutils.Allow, }, + { + Service: "compute", + Resource: "cloudproviders", + Action: PolicyActionList, + Result: rbacutils.Allow, + }, + { + Service: "compute", + Resource: "cloudproviders", + Action: PolicyActionGet, + Result: rbacutils.Allow, + }, }, }, { diff --git a/pkg/cloudcommon/policy/resources.go b/pkg/cloudcommon/policy/resources.go index cc06c76f6d..26fea09dc5 100644 --- a/pkg/cloudcommon/policy/resources.go +++ b/pkg/cloudcommon/policy/resources.go @@ -43,6 +43,7 @@ var ( } computeDomainResources = []string{ "cloudaccounts", + "cloudproviders", "recyclebins", } computeUserResources = []string{ diff --git a/pkg/compute/models/capabilities.go b/pkg/compute/models/capabilities.go index 1e9bebf0de..ba47463d5d 100644 --- a/pkg/compute/models/capabilities.go +++ b/pkg/compute/models/capabilities.go @@ -127,7 +127,7 @@ func getDomainManagerSubq(domainId string) *sqlchemy.SSubQuery { q = q.Join(accounts, sqlchemy.Equals(accounts.Field("id"), providers.Field("cloudaccount_id"))) q = q.Filter(sqlchemy.OR( sqlchemy.Equals(accounts.Field("domain_id"), domainId), - sqlchemy.IsTrue(accounts.Field("is_public")), + sqlchemy.Equals(accounts.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM), )) q = q.Filter(sqlchemy.Equals(accounts.Field("status"), api.CLOUD_PROVIDER_CONNECTED)) q = q.Filter(sqlchemy.IsTrue(accounts.Field("enabled"))) @@ -180,20 +180,27 @@ func getDBInstanceInfo(region *SCloudregion, zone *SZone) map[string]map[string] func getBrands(region *SCloudregion, zone *SZone, domainId string, hypervisors []string, ) ([]string, []string, []string, []string) { q := CloudaccountManager.Query().IsTrue("enabled") + providers := CloudproviderManager.Query().SubQuery() + q = q.Join(providers, sqlchemy.Equals(q.Field("id"), providers.Field("cloudaccount_id"))) if zone != nil { region = zone.GetRegion() } if region != nil { - providers := CloudproviderManager.Query().SubQuery() providerregions := CloudproviderRegionManager.Query().SubQuery() - q = q.Join(providers, sqlchemy.Equals(q.Field("id"), providers.Field("cloudaccount_id"))) q = q.Join(providerregions, sqlchemy.Equals(providers.Field("id"), providerregions.Field("cloudprovider_id"))) q = q.Filter(sqlchemy.Equals(providerregions.Field("cloudregion_id"), region.Id)) } if len(domainId) > 0 { q = q.Filter(sqlchemy.OR( - sqlchemy.IsTrue(q.Field("is_public")), - sqlchemy.Equals(q.Field("domain_id"), domainId), + sqlchemy.AND( + sqlchemy.Equals(q.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN), + sqlchemy.Equals(q.Field("domain_id"), domainId), + ), + sqlchemy.Equals(q.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM), + sqlchemy.AND( + sqlchemy.Equals(q.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN), + sqlchemy.Equals(providers.Field("domain_id"), domainId), + ), )) } cloudAccounts := make([]SCloudaccount, 0) diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index feb4552dd0..bf0b8111d1 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -37,6 +37,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/options" @@ -52,7 +53,6 @@ import ( type SCloudaccountManager struct { db.SEnabledStatusStandaloneResourceBaseManager db.SDomainizedResourceBaseManager - db.SSharableBaseResourceManager } var CloudaccountManager *SCloudaccountManager @@ -72,9 +72,8 @@ func init() { type SCloudaccount struct { db.SEnabledStatusStandaloneResourceBase db.SDomainizedResourceBase - db.SSharableBaseResource - SSyncableBaseResource + LastAutoSync time.Time `list:"domain"` ProjectId string `name:"tenant_id" width:"128" charset:"ascii" list:"user" create:"domain_optional"` @@ -112,6 +111,11 @@ type SCloudaccount struct { Brand string `width:"64" charset:"utf8" nullable:"true" list:"domain" create:"optional"` Options *jsonutils.JSONDict `get:"domain" create:"domain_optional" update:"domain"` + + // for backward compatiblity, keep is_public field, but not usable + IsPublic bool `default:"false" nullable:"false"` + // add share_mode field to indicate the share range of this account + ShareMode string `width:"32" charset:"ascii" nullable:"true" list:"domain"` } func (self *SCloudaccountManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { @@ -367,6 +371,7 @@ func (self *SCloudaccount) CustomizeCreate(ctx context.Context, userCred mcclien } self.DomainId = ownerId.GetProjectDomainId() // self.EnableAutoSync = false + self.ShareMode = api.CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN return self.SEnabledStatusStandaloneResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) } @@ -963,7 +968,7 @@ func migrateCloudprovider(cloudprovider *SCloudprovider) error { return nil } -func (manager *SCloudaccountManager) InitializeBrand() error { +func (manager *SCloudaccountManager) initializeBrand() error { accounts := []SCloudaccount{} q := manager.Query().IsNullOrEmpty("brand") err := db.FetchModelObjects(manager, q, &accounts) @@ -984,6 +989,31 @@ func (manager *SCloudaccountManager) InitializeBrand() error { return nil } +func (manager *SCloudaccountManager) initializeShareMode() error { + accounts := []SCloudaccount{} + q := manager.Query().IsNullOrEmpty("share_mode") + err := db.FetchModelObjects(manager, q, &accounts) + if err != nil { + log.Errorf("fetch all clound account fail %v", err) + return err + } + for i := 0; i < len(accounts); i++ { + account := &accounts[i] + _, err = db.Update(account, func() error { + if account.IsPublic { + account.ShareMode = api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM + } else { + account.ShareMode = api.CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN + } + return nil + }) + if err != nil { + return err + } + } + return nil +} + func (manager *SCloudaccountManager) InitializeData() error { cloudproviders := []SCloudprovider{} q := CloudproviderManager.Query() @@ -999,7 +1029,15 @@ func (manager *SCloudaccountManager) InitializeData() error { return err } } - return manager.InitializeBrand() + err = manager.initializeBrand() + if err != nil { + return err + } + err = manager.initializeShareMode() + if err != nil { + return err + } + return nil } func (self *SCloudaccount) GetBalance() (float64, error) { @@ -1521,24 +1559,126 @@ func (manager *SCloudaccountManager) FetchCustomizeColumns(ctx context.Context, return rows } +func (account *SCloudaccount) setShareMode(userCred mcclient.TokenCredential, mode string) error { + if account.ShareMode == mode { + return nil + } + diff, err := db.Update(account, func() error { + account.ShareMode = mode + return nil + }) + if err != nil { + return errors.Wrap(err, "db.Update") + } + db.OpsLog.LogEvent(account, db.ACT_UPDATE, diff, userCred) + return nil +} + func (account *SCloudaccount) AllowPerformPublic(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return db.SharableAllowPerformPublic(account, userCred) + return db.IsAllowPerform(rbacutils.ScopeSystem, userCred, account, "public") } func (account *SCloudaccount) PerformPublic(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - res, err := db.SharablePerformPublic(account, ctx, userCred, query, data) - return res, err + scope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), account.GetModelManager().KeywordPlural(), policy.PolicyActionPerform, "public") + if scope != rbacutils.ScopeSystem { + return nil, httperrors.NewForbiddenError("not enough privilege") + } + + err := account.setShareMode(userCred, api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + return nil, nil } func (account *SCloudaccount) AllowPerformPrivate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return db.SharableAllowPerformPrivate(account, userCred) + return db.IsAllowPerform(rbacutils.ScopeSystem, userCred, account, "private") } func (account *SCloudaccount) PerformPrivate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - res, err := db.SharablePerformPrivate(account, ctx, userCred, query, data) - return res, err + providers := account.GetCloudproviders() + for i := range providers { + if providers[i].DomainId != account.DomainId { + return nil, httperrors.NewConflictError("provider is shared outside of domain") + } + } + scope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), account.GetModelManager().KeywordPlural(), policy.PolicyActionPerform, "private") + if scope != rbacutils.ScopeSystem { + return nil, httperrors.NewForbiddenError("not enough privilege") + } + + err := account.setShareMode(userCred, api.CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + return nil, nil +} + +func (account *SCloudaccount) AllowPerformShareMode(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAllowPerform(rbacutils.ScopeSystem, userCred, account, "share-mode") +} + +func (account *SCloudaccount) PerformShareMode(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + input := api.CloudaccountShareModeInput{} + err := data.Unmarshal(&input) + if err != nil { + return nil, httperrors.NewInputParameterError("fail to unmarshal input: %s", err) + } + err = input.Validate() + if err != nil { + return nil, err + } + if account.ShareMode == input.ShareMode { + return nil, nil + } + + if input.ShareMode == api.CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN { + providers := account.GetCloudproviders() + for i := range providers { + if providers[i].DomainId != account.DomainId { + return nil, httperrors.NewConflictError("provider is shared outside of domain") + } + } + } + + scope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), account.GetModelManager().KeywordPlural(), policy.PolicyActionPerform, "share-mode") + if scope != rbacutils.ScopeSystem { + return nil, httperrors.NewForbiddenError("not enough privilege") + } + + err = account.setShareMode(userCred, input.ShareMode) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + return nil, nil } func (manager *SCloudaccountManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery { - return manager.SSharableBaseResourceManager.FilterByOwner(q, owner, scope) + if owner != nil { + switch scope { + case rbacutils.ScopeProject, rbacutils.ScopeDomain: + if len(owner.GetProjectDomainId()) > 0 { + cloudproviders := CloudproviderManager.Query().SubQuery() + q = q.Join(cloudproviders, sqlchemy.Equals( + q.Field("id"), + cloudproviders.Field("cloudaccount_id"), + )) + q = q.Filter(sqlchemy.OR( + sqlchemy.AND( + sqlchemy.Equals(q.Field("domain_id"), owner.GetProjectDomainId()), + sqlchemy.Equals(q.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN), + ), + sqlchemy.Equals(q.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM), + sqlchemy.AND( + sqlchemy.Equals(q.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN), + sqlchemy.Equals(cloudproviders.Field("domain_id"), owner.GetProjectDomainId()), + ), + )) + } + } + } + return q } diff --git a/pkg/compute/models/cloudproviders.go b/pkg/compute/models/cloudproviders.go index 4bdd48e2d9..3092c7ed5f 100644 --- a/pkg/compute/models/cloudproviders.go +++ b/pkg/compute/models/cloudproviders.go @@ -554,8 +554,15 @@ func (self *SCloudprovider) PerformChangeProject(ctx context.Context, userCred m return nil, nil } + account := self.GetCloudaccount() if self.DomainId != tenant.DomainId { - return nil, httperrors.NewForbiddenError("not allow change project across domain") + if !db.IsAdminAllowPerform(userCred, self, "change-project") { + return nil, httperrors.NewForbiddenError("not allow to change project across domain") + } + if account.ShareMode == api.CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN { + return nil, httperrors.NewInvalidStatusError("not a public cloud account") + } + // otherwise, allow change project across domain } notes := struct { @@ -582,7 +589,7 @@ func (self *SCloudprovider) PerformChangeProject(ctx context.Context, userCred m logclient.AddSimpleActionLog(self, logclient.ACT_CHANGE_OWNER, notes, userCred, true) - if self.GetCloudaccount().EnableAutoSync { // no need to sync rightnow, will do it in auto sync + if account.EnableAutoSync { // no need to sync rightnow, will do it in auto sync return nil, nil } @@ -1295,32 +1302,20 @@ func (manager *SCloudproviderManager) FetchCustomizeColumns(ctx context.Context, func (manager *SCloudproviderManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery { if owner != nil { - // log.Debugf("SCloudproviderManager.FilterByOwner scope:%s project:%s domain:%s", scope, owner.GetProjectId(), owner.GetProjectDomainId()) switch scope { - case rbacutils.ScopeProject: - if len(owner.GetProjectId()) > 0 { - subq := CloudaccountManager.Query("id") - subq = CloudaccountManager.FilterByOwner(subq, owner, scope) - q = q.Filter(sqlchemy.OR( - sqlchemy.Equals(q.Field("tenant_id"), owner.GetProjectId()), - sqlchemy.In(q.Field("cloudaccount_id"), subq.SubQuery()), - )) - } - case rbacutils.ScopeDomain: + case rbacutils.ScopeProject, rbacutils.ScopeDomain: if len(owner.GetProjectDomainId()) > 0 { - subq := CloudaccountManager.Query("id") - subq = CloudaccountManager.FilterByOwner(subq, owner, scope) + cloudaccounts := CloudaccountManager.Query().SubQuery() + q = q.Join(cloudaccounts, sqlchemy.Equals( + q.Field("cloudaccount_id"), + cloudaccounts.Field("id"), + )) q = q.Filter(sqlchemy.OR( sqlchemy.Equals(q.Field("domain_id"), owner.GetProjectDomainId()), - sqlchemy.In(q.Field("cloudaccount_id"), subq.SubQuery()), + sqlchemy.Equals(cloudaccounts.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM), )) } } - /*if len(owner.GetProjectId()) > 0 { - q = q.Equals("tenant_id", owner.GetProjectId()) - } else if len(owner.GetProjectDomainId()) > 0 { - q = q.Equals("domain_id", owner.GetProjectDomainId()) - }*/ } return q } diff --git a/pkg/compute/models/managedresource.go b/pkg/compute/models/managedresource.go index 2f41a1b228..f41e92fb31 100644 --- a/pkg/compute/models/managedresource.go +++ b/pkg/compute/models/managedresource.go @@ -160,7 +160,10 @@ func managedResourceFilterByDomain(q *sqlchemy.SQuery, query jsonutils.JSONObjec providers := CloudproviderManager.Query().SubQuery() subq := providers.Query(providers.Field("id")) subq = subq.Join(accounts, sqlchemy.Equals(providers.Field("cloudaccount_id"), accounts.Field("id"))) - subq = subq.Filter(sqlchemy.Equals(accounts.Field("domain_id"), domain.GetId())) + subq = subq.Filter(sqlchemy.OR( + sqlchemy.Equals(providers.Field("domain_id"), domain.GetId()), + sqlchemy.Equals(accounts.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM), + )) if len(filterField) == 0 { q = q.Filter(sqlchemy.OR( sqlchemy.IsNullOrEmpty(q.Field("manager_id")), diff --git a/pkg/compute/usages/handler.go b/pkg/compute/usages/handler.go index ef242fb674..0e4d6c907a 100644 --- a/pkg/compute/usages/handler.go +++ b/pkg/compute/usages/handler.go @@ -106,7 +106,7 @@ func rangeObjHandler( return } isOwner := false - if scope == rbacutils.ScopeDomain && obj != nil && db.IsObjectRbacAllowed(obj, userCred, policy.PolicyActionGet, "usage") { + if scope == rbacutils.ScopeDomain && obj != nil && db.IsObjectRbacAllowed(obj, userCred, policy.PolicyActionGet, "usage") == nil { isOwner = true } log.Debugf("%s %v %s", ownerId, isOwner, scope) diff --git a/pkg/keystone/models/roles.go b/pkg/keystone/models/roles.go index c3674ea5f7..3e56a7dc93 100644 --- a/pkg/keystone/models/roles.go +++ b/pkg/keystone/models/roles.go @@ -19,10 +19,9 @@ import ( "database/sql" "fmt" - "github.com/pkg/errors" - "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/sqlchemy" api "yunion.io/x/onecloud/pkg/apis/identity" diff --git a/pkg/mcclient/modules/mod_cloudaccounts.go b/pkg/mcclient/modules/mod_cloudaccounts.go index f334606206..77c9e834fe 100644 --- a/pkg/mcclient/modules/mod_cloudaccounts.go +++ b/pkg/mcclient/modules/mod_cloudaccounts.go @@ -28,7 +28,7 @@ func init() { "guest_count", "project_domain", "domain_id", "Provider", "Brand", "Enable_Auto_Sync", "Sync_Interval_Seconds", - "Is_Public"}, + "Share_Mode"}, []string{}) registerCompute(&Cloudaccounts) diff --git a/pkg/mcclient/options/cloudaccounts.go b/pkg/mcclient/options/cloudaccounts.go index b1648ac4a2..38850bd77e 100644 --- a/pkg/mcclient/options/cloudaccounts.go +++ b/pkg/mcclient/options/cloudaccounts.go @@ -82,7 +82,7 @@ type SCloudAccountCreateBaseOptions struct { SyncIntervalSeconds int `help:"Interval to synchronize if auto sync is enable" metavar:"SECONDS"` - ProjectDomain string `help:"domain for this account, default is Default" default:"Default"` + ProjectDomain string `help:"domain for this account"` } type SVMwareCloudAccountCreateOptions struct { diff --git a/pkg/scheduler/cache/candidate/base.go b/pkg/scheduler/cache/candidate/base.go index 83e1441c8d..92f84707e7 100644 --- a/pkg/scheduler/cache/candidate/base.go +++ b/pkg/scheduler/cache/candidate/base.go @@ -83,7 +83,7 @@ func (b baseHostGetter) IsPublic() bool { if account == nil { return false } - return account.GetIsPublic() + return account.ShareMode == computeapi.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM } func (b baseHostGetter) DomainId() string {