From 7bfdc0dce4f134fafec4024d3793cba3d17d2019 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Fri, 10 Apr 2020 20:59:13 +0800 Subject: [PATCH] fix: cascading sharing problems --- cmd/climc/shell/hosts.go | 7 +- cmd/climc/shell/networks.go | 14 +++- cmd/climc/shell/policies.go | 22 +++--- cmd/climc/shell/vpcs.go | 36 +++++++++ cmd/climc/shell/wires.go | 37 +++++++++ pkg/apis/compute/cloudprovider.go | 4 - pkg/apis/identity/input.go | 3 + pkg/apis/identity/policy.go | 3 + pkg/apis/identity/role.go | 3 + pkg/apis/input.go | 16 ++-- pkg/apis/output.go | 4 + pkg/cloudcommon/db/domain.go | 6 ++ pkg/cloudcommon/db/domainresource.go | 17 ++++- pkg/cloudcommon/db/infraresource.go | 22 ++++-- pkg/cloudcommon/db/interface.go | 6 +- pkg/cloudcommon/db/managed.go | 30 +++++++- pkg/cloudcommon/db/quotas/register.go | 1 - pkg/cloudcommon/db/sharablebase.go | 91 +++++++++++++++++++++-- pkg/cloudcommon/db/sharablebase_test.go | 68 +++++++++++++++++ pkg/cloudcommon/db/sharablevirtual.go | 18 +++-- pkg/cloudcommon/db/sharedresource.go | 23 +++++- pkg/cloudcommon/db/virtualresource.go | 15 +++- pkg/compute/models/cloudaccounts.go | 34 ++++++--- pkg/compute/models/globalvpcs.go | 12 +++ pkg/compute/models/hosts.go | 24 ++++++ pkg/compute/models/managedresource.go | 21 +----- pkg/compute/models/networks.go | 11 +++ pkg/compute/models/storages.go | 24 +++++- pkg/compute/models/vpcs.go | 27 ++++++- pkg/compute/models/wires.go | 23 ++++++ pkg/image/models/images.go | 20 +++++ pkg/keystone/models/policies.go | 14 ++++ pkg/keystone/models/roles.go | 14 ++++ pkg/mcclient/modules/mod_buckets.go | 2 +- pkg/mcclient/modules/mod_cloudaccounts.go | 3 +- pkg/mcclient/modules/mod_globalvpcs.go | 2 +- pkg/mcclient/modules/mod_hosts.go | 1 + pkg/mcclient/modules/mod_policies.go | 4 +- pkg/mcclient/modules/mod_storages.go | 2 +- pkg/mcclient/modules/mod_vpcs.go | 2 +- pkg/mcclient/modules/mod_wires.go | 2 +- pkg/util/stringutils2/sortedstrings.go | 18 +++++ 42 files changed, 603 insertions(+), 103 deletions(-) create mode 100644 pkg/cloudcommon/db/sharablebase_test.go diff --git a/cmd/climc/shell/hosts.go b/cmd/climc/shell/hosts.go index 837386f593..4aab10dc86 100644 --- a/cmd/climc/shell/hosts.go +++ b/cmd/climc/shell/hosts.go @@ -725,10 +725,9 @@ func init() { }) type HostPublicOptions struct { - ID string `help:"ID or name of host" json:"-"` - Scope string `help:"sharing scope" choices:"system|domain|project"` - SharedProjects []string `help:"Share to prjects"` - SharedDomains []string `help:"share to domains"` + ID string `help:"ID or name of host" json:"-"` + Scope string `help:"sharing scope" choices:"system|domain"` + SharedDomains []string `help:"share to domains"` } R(&HostPublicOptions{}, "host-public", "Make a host public", func(s *mcclient.ClientSession, args *HostPublicOptions) error { params := jsonutils.Marshal(args) diff --git a/cmd/climc/shell/networks.go b/cmd/climc/shell/networks.go index c6f0ab5b6d..03a243581d 100644 --- a/cmd/climc/shell/networks.go +++ b/cmd/climc/shell/networks.go @@ -185,10 +185,7 @@ func init() { SharedDomains []string `help:"share to domains"` } R(&NetworkShareOptions{}, "network-public", "Make a network public", func(s *mcclient.ClientSession, args *NetworkShareOptions) error { - params, err := options.StructToParams(args) - if err != nil { - return err - } + params := jsonutils.Marshal(args) result, err := modules.Networks.PerformAction(s, args.ID, "public", params) if err != nil { return err @@ -399,4 +396,13 @@ func init() { printObject(result) return nil }) + + R(&NetworkIdOptions{}, "network-change-owner-candidate-domains", "Show candiate domains of a network for changing owner", func(s *mcclient.ClientSession, args *NetworkIdOptions) error { + result, err := modules.Networks.GetSpecific(s, args.ID, "change-owner-candidate-domains", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) } diff --git a/cmd/climc/shell/policies.go b/cmd/climc/shell/policies.go index c8d0cb78ba..208b5be46d 100644 --- a/cmd/climc/shell/policies.go +++ b/cmd/climc/shell/policies.go @@ -136,11 +136,14 @@ func init() { R(&PolicyPatchOptions{}, "policy-patch", "Patch policy", updateFunc) R(&PolicyPatchOptions{}, "policy-update", "Update policy", updateFunc) - type PolicyPerformOptions struct { - ID string `help:"ID of policy to update"` + type PolicyPublicOptions struct { + ID string `help:"ID of policy to update" json:"-"` + Scope string `help:"sharing scope" choices:"system|domain"` + SharedDomains []string `help:"share to domains"` } - R(&PolicyPerformOptions{}, "policy-public", "Mark a policy public", func(s *mcclient.ClientSession, args *PolicyPerformOptions) error { - result, err := modules.Policies.PerformAction(s, args.ID, "public", nil) + R(&PolicyPublicOptions{}, "policy-public", "Mark a policy public", func(s *mcclient.ClientSession, args *PolicyPublicOptions) error { + params := jsonutils.Marshal(args) + result, err := modules.Policies.PerformAction(s, args.ID, "public", params) if err != nil { return err } @@ -148,7 +151,10 @@ func init() { return nil }) - R(&PolicyPerformOptions{}, "policy-private", "Mark a policy private", func(s *mcclient.ClientSession, args *PolicyPerformOptions) error { + type PolicyPrivateOptions struct { + ID string `help:"ID of policy to update" json:"-"` + } + R(&PolicyPrivateOptions{}, "policy-private", "Mark a policy private", func(s *mcclient.ClientSession, args *PolicyPrivateOptions) error { result, err := modules.Policies.PerformAction(s, args.ID, "private", nil) if err != nil { return err @@ -184,11 +190,7 @@ func init() { if err != nil { return err } - yaml, err := result.GetString("policy") - if err != nil { - return err - } - fmt.Println(yaml) + printObject(result) return nil }) diff --git a/cmd/climc/shell/vpcs.go b/cmd/climc/shell/vpcs.go index cfa1bc7c71..c24c406743 100644 --- a/cmd/climc/shell/vpcs.go +++ b/cmd/climc/shell/vpcs.go @@ -178,4 +178,40 @@ func init() { return nil }) + type VpcPublicOptions struct { + ID string `help:"ID or name of vpc" json:"-"` + Scope string `help:"sharing scope" choices:"system|domain"` + SharedDomains []string `help:"share to domains"` + } + R(&VpcPublicOptions{}, "vpc-public", "Make vpc public", func(s *mcclient.ClientSession, args *VpcPublicOptions) error { + params := jsonutils.Marshal(args) + result, err := modules.Vpcs.PerformAction(s, args.ID, "public", params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type VpcPrivateOptions struct { + ID string `help:"ID or name of vpc" json:"-"` + } + R(&VpcPrivateOptions{}, "vpc-private", "Make vpc private", func(s *mcclient.ClientSession, args *VpcPrivateOptions) error { + params := jsonutils.Marshal(args) + result, err := modules.Vpcs.PerformAction(s, args.ID, "private", params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&VpcShowOptions{}, "vpc-change-owner-candidate-domains", "Show candiate domains of a vpc for changing owner", func(s *mcclient.ClientSession, args *VpcShowOptions) error { + result, err := modules.Vpcs.GetSpecific(s, args.ID, "change-owner-candidate-domains", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) } diff --git a/cmd/climc/shell/wires.go b/cmd/climc/shell/wires.go index 42ef39111e..414a905014 100644 --- a/cmd/climc/shell/wires.go +++ b/cmd/climc/shell/wires.go @@ -132,4 +132,41 @@ func init() { printObject(result) return nil }) + + type WirePublicOptions struct { + ID string `help:"ID or name of wire" json:"-"` + Scope string `help:"sharing scope" choices:"system|domain"` + SharedDomains []string `help:"share to domains"` + } + R(&WirePublicOptions{}, "wire-public", "Make wire public", func(s *mcclient.ClientSession, args *WirePublicOptions) error { + params := jsonutils.Marshal(args) + result, err := modules.Wires.PerformAction(s, args.ID, "public", params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type WirePrivateOptions struct { + ID string `help:"ID or name of wire" json:"-"` + } + R(&WirePrivateOptions{}, "wire-private", "Make wire private", func(s *mcclient.ClientSession, args *WirePrivateOptions) error { + params := jsonutils.Marshal(args) + result, err := modules.Wires.PerformAction(s, args.ID, "private", params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&WireShowOptions{}, "wire-change-owner-candidate-domains", "Show candiate domains of a wire for changing owner", func(s *mcclient.ClientSession, args *WireShowOptions) error { + result, err := modules.Wires.GetSpecific(s, args.ID, "change-owner-candidate-domains", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) } diff --git a/pkg/apis/compute/cloudprovider.go b/pkg/apis/compute/cloudprovider.go index e251b58197..2ad34576c3 100644 --- a/pkg/apis/compute/cloudprovider.go +++ b/pkg/apis/compute/cloudprovider.go @@ -247,7 +247,3 @@ type CloudproviderUpdateInput struct { type CloudproviderCreateInput struct { } - -type ChangeOwnerCandidateDomainsOutput struct { - Candidates []apis.SharedDomain `json:"candidates"` -} diff --git a/pkg/apis/identity/input.go b/pkg/apis/identity/input.go index 497de73b33..a1569757a7 100644 --- a/pkg/apis/identity/input.go +++ b/pkg/apis/identity/input.go @@ -438,6 +438,7 @@ type GroupCreateInput struct { type PolicyCreateInput struct { EnabledIdentityBaseResourceCreateInput + apis.SharableResourceBaseCreateInput Type string `json:"type"` @@ -446,4 +447,6 @@ type PolicyCreateInput struct { type RoleCreateInput struct { IdentityBaseResourceCreateInput + + apis.SharableResourceBaseCreateInput } diff --git a/pkg/apis/identity/policy.go b/pkg/apis/identity/policy.go index ec82d823ca..bdf02a7ae3 100644 --- a/pkg/apis/identity/policy.go +++ b/pkg/apis/identity/policy.go @@ -14,8 +14,11 @@ package identity +import "yunion.io/x/onecloud/pkg/apis" + type PolicyDetails struct { EnabledIdentityBaseResourceDetails + apis.SharableResourceBaseInfo SPolicy } diff --git a/pkg/apis/identity/role.go b/pkg/apis/identity/role.go index 7d1567464a..69f2fa4894 100644 --- a/pkg/apis/identity/role.go +++ b/pkg/apis/identity/role.go @@ -14,8 +14,11 @@ package identity +import "yunion.io/x/onecloud/pkg/apis" + type RoleDetails struct { IdentityBaseResourceDetails + apis.SharableResourceBaseInfo SRole diff --git a/pkg/apis/input.go b/pkg/apis/input.go index 5b6d850fd2..383cb00743 100644 --- a/pkg/apis/input.go +++ b/pkg/apis/input.go @@ -59,18 +59,22 @@ type ProjectizedResourceCreateInput struct { ProjectizedResourceInput } -type SharableVirtualResourceCreateInput struct { - VirtualResourceCreateInput - - // description: indicate the resource is a public resource +type SharableResourceBaseCreateInput struct { + // 是否共享 // required: false IsPublic *bool `json:"is_public"` - // description: indicate the shared scope for a public resource, which can be domain or system or none + // 共享范围 // required: false PublicScope string `json:"public_scope"` } +type SharableVirtualResourceCreateInput struct { + VirtualResourceCreateInput + + SharableResourceBaseCreateInput +} + type VirtualResourceCreateInput struct { StatusStandaloneResourceCreateInput ProjectizedResourceCreateInput @@ -207,6 +211,8 @@ type PerformDisableInput struct { type InfrasResourceBaseCreateInput struct { DomainLevelResourceCreateInput + + SharableResourceBaseCreateInput } type StatusInfrasResourceBaseCreateInput struct { diff --git a/pkg/apis/output.go b/pkg/apis/output.go index 43f3d6bda8..f27b744c37 100644 --- a/pkg/apis/output.go +++ b/pkg/apis/output.go @@ -156,3 +156,7 @@ type StatusInfrasResourceBaseDetails struct { type EnabledStatusInfrasResourceBaseDetails struct { StatusInfrasResourceBaseDetails } + +type ChangeOwnerCandidateDomainsOutput struct { + Candidates []SharedDomain `json:"candidates"` +} diff --git a/pkg/cloudcommon/db/domain.go b/pkg/cloudcommon/db/domain.go index bde30f3dd1..c43efeb4f1 100644 --- a/pkg/cloudcommon/db/domain.go +++ b/pkg/cloudcommon/db/domain.go @@ -69,6 +69,12 @@ func (model *SDomainizedResourceBase) GetOwnerId() mcclient.IIdentityProvider { return &owner } +// returns candiate domain Id list that the resource can change owner to +// nil or empty means any domain +func (model *SDomainizedResourceBase) GetChangeOwnerCandidateDomainIds() []string { + return nil +} + func ValidateCreateDomainId(domainId string) error { if !consts.GetNonDefaultDomainProjects() && domainId != identity.DEFAULT_DOMAIN_ID { return httperrors.NewForbiddenError("project in non-default domain is prohibited") diff --git a/pkg/cloudcommon/db/domainresource.go b/pkg/cloudcommon/db/domainresource.go index 4f6630a7f2..2717d815c8 100644 --- a/pkg/cloudcommon/db/domainresource.go +++ b/pkg/cloudcommon/db/domainresource.go @@ -17,6 +17,8 @@ package db import ( "context" + "yunion.io/x/pkg/utils" + "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" @@ -153,10 +155,9 @@ func (model *SDomainLevelResourceBase) PerformChangeOwner(ctx context.Context, u } // change domain, do check - if managed, ok := model.GetIDomainLevelModel().(IManagedResoucceBase); ok { - if !managed.CanShareToDomain(ownerId.GetProjectDomainId()) { - return nil, errors.Wrap(httperrors.ErrForbidden, "cann't share across domain") - } + candidates := model.GetIDomainLevelModel().GetChangeOwnerCandidateDomainIds() + if len(candidates) > 0 && !utils.IsInStringArray(ownerId.GetProjectDomainId(), candidates) { + return nil, errors.Wrap(httperrors.ErrForbidden, "target domain not in change owner candidate list") } if !IsAdminAllowPerform(userCred, model, "change-owner") { @@ -299,3 +300,11 @@ func (model *SDomainLevelResourceBase) ValidateUpdateData( } return input, nil } + +func (model *SDomainLevelResourceBase) AllowGetDetailsChangeOwnerCandidateDomains(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return model.IsOwner(userCred) || IsAdminAllowGetSpec(userCred, model, "change-owner-candidate-domains") +} + +func (model *SDomainLevelResourceBase) GetDetailsChangeOwnerCandidateDomains(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (apis.ChangeOwnerCandidateDomainsOutput, error) { + return IOwnerResourceBaseModelGetChangeOwnerCandidateDomains(model.GetIDomainLevelModel()) +} diff --git a/pkg/cloudcommon/db/infraresource.go b/pkg/cloudcommon/db/infraresource.go index 6c6a086195..ee46add44a 100644 --- a/pkg/cloudcommon/db/infraresource.go +++ b/pkg/cloudcommon/db/infraresource.go @@ -47,7 +47,7 @@ func NewInfrasResourceBaseManager( type SInfrasResourceBase struct { SDomainLevelResourceBase - SSharableBaseResource + SSharableBaseResource `"is_public=>create":"domain_optional" "public_scope=>create":"domain_optional"` } func (manager *SInfrasResourceBaseManager) GetIInfrasModelManager() IInfrasModelManager { @@ -75,7 +75,7 @@ func (model *SInfrasResourceBase) AllowPerformPublic(ctx context.Context, userCr } func (model *SInfrasResourceBase) PerformPublic(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPublicInput) (jsonutils.JSONObject, error) { - err := SharablePerformPublic(model, ctx, userCred, input) + err := SharablePerformPublic(model.GetIInfrasModel(), ctx, userCred, input) if err != nil { return nil, errors.Wrap(err, "SharablePerformPublic") } @@ -87,7 +87,7 @@ func (model *SInfrasResourceBase) AllowPerformPrivate(ctx context.Context, userC } func (model *SInfrasResourceBase) PerformPrivate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPrivateInput) (jsonutils.JSONObject, error) { - err := SharablePerformPrivate(model, ctx, userCred) + err := SharablePerformPrivate(model.GetIInfrasModel(), ctx, userCred) if err != nil { return nil, errors.Wrap(err, "SharablePerformPrivate") } @@ -230,13 +230,13 @@ func (model *SInfrasResourceBase) SyncShareState(ctx context.Context, userCred m } else if len(shareInfo.SharedDomains) > 0 { model.IsPublic = true model.PublicScope = string(rbacutils.ScopeDomain) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetProject, nil) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetDomain, shareInfo.SharedDomains) + SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetProject, nil, nil, nil) + SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetDomain, shareInfo.SharedDomains, nil, nil) } else { model.IsPublic = false model.PublicScope = string(rbacutils.ScopeNone) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetProject, nil) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetDomain, nil) + SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetProject, nil, nil, nil) + SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetDomain, nil, nil, nil) } } return nil @@ -246,3 +246,11 @@ func (model *SInfrasResourceBase) SyncShareState(ctx context.Context, userCred m } } } + +func (model *SInfrasResourceBase) GetSharableTargetDomainIds() []string { + return model.GetIInfrasModel().GetChangeOwnerCandidateDomainIds() +} + +func (model *SInfrasResourceBase) GetRequiredSharedDomainIds() []string { + return []string{model.DomainId} +} diff --git a/pkg/cloudcommon/db/interface.go b/pkg/cloudcommon/db/interface.go index 731f77e188..67f887adcc 100644 --- a/pkg/cloudcommon/db/interface.go +++ b/pkg/cloudcommon/db/interface.go @@ -295,6 +295,8 @@ type IDomainLevelModel interface { SyncCloudDomainId(userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider) GetIDomainLevelModel() IDomainLevelModel + + IOwnerResourceBaseModel } type IInfrasModelManager interface { @@ -308,7 +310,6 @@ type IInfrasModel interface { ISharableBase GetIInfrasModel() IInfrasModel - GetSharedDomains() []string } type IVirtualModelManager interface { @@ -328,6 +329,8 @@ type IVirtualModel interface { SyncCloudProjectId(userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider) GetIVirtualModel() IVirtualModel + + IOwnerResourceBaseModel } type ISharableVirtualModelManager interface { @@ -342,7 +345,6 @@ type ISharableVirtualModel interface { GetISharableVirtualModel() ISharableVirtualModel GetSharedProjects() []string - GetSharedDomains() []string } type IAdminSharableVirtualModelManager interface { diff --git a/pkg/cloudcommon/db/managed.go b/pkg/cloudcommon/db/managed.go index 343c096273..5c29d1f648 100644 --- a/pkg/cloudcommon/db/managed.go +++ b/pkg/cloudcommon/db/managed.go @@ -14,6 +14,32 @@ package db -type IManagedResoucceBase interface { - CanShareToDomain(domainId string) bool +import ( + "yunion.io/x/onecloud/pkg/apis" + "yunion.io/x/pkg/errors" +) + +type IOwnerResourceBaseModel interface { + GetChangeOwnerCandidateDomainIds() []string +} + +func IOwnerResourceBaseModelGetChangeOwnerCandidateDomains(model IOwnerResourceBaseModel) (apis.ChangeOwnerCandidateDomainsOutput, error) { + output := apis.ChangeOwnerCandidateDomainsOutput{} + candidateIds := model.GetChangeOwnerCandidateDomainIds() + if len(candidateIds) == 0 { + return output, nil + } + domainMap := make(map[string]STenant) + err := FetchQueryObjectsByIds(TenantCacheManager.GetDomainQuery(), "id", candidateIds, &domainMap) + if err != nil { + return output, errors.Wrap(err, "FetchQueryObjectsByIds") + } + output.Candidates = make([]apis.SharedDomain, len(candidateIds)) + for i := range candidateIds { + output.Candidates[i].Id = candidateIds[i] + if domain, ok := domainMap[candidateIds[i]]; ok { + output.Candidates[i].Name = domain.Name + } + } + return output, nil } diff --git a/pkg/cloudcommon/db/quotas/register.go b/pkg/cloudcommon/db/quotas/register.go index 532b13b607..7b8e607537 100644 --- a/pkg/cloudcommon/db/quotas/register.go +++ b/pkg/cloudcommon/db/quotas/register.go @@ -85,7 +85,6 @@ func cancelUsage(ctx context.Context, userCred mcclient.TokenCredential, usage I if err != nil { log.Errorf("cancelUsage %s fail: %s", jsonutils.Marshal(usage), err) } - log.Infof("cancelUsage %s", jsonutils.Marshal(usage)) } func GetQuotaCount(ctx context.Context, request IQuota, pendingKeys IQuotaKeys) (int, error) { diff --git a/pkg/cloudcommon/db/sharablebase.go b/pkg/cloudcommon/db/sharablebase.go index afb694292b..a384d114ae 100644 --- a/pkg/cloudcommon/db/sharablebase.go +++ b/pkg/cloudcommon/db/sharablebase.go @@ -217,6 +217,62 @@ type ISharableBase interface { SetShare(pub bool, scoe rbacutils.TRbacScope) GetIsPublic() bool GetPublicScope() rbacutils.TRbacScope + GetSharableTargetDomainIds() []string + GetRequiredSharedDomainIds() []string + GetSharedDomains() []string +} + +func ISharableChangeOwnerCandidateDomainIds(model ISharableBaseModel) []string { + var candidates []string + if model.GetIsPublic() { + switch model.GetPublicScope() { + case rbacutils.ScopeSystem: + return candidates + case rbacutils.ScopeDomain: + candidates = model.GetSharedDomains() + } + } + ownerId := model.GetOwnerId() + if ownerId != nil && len(ownerId.GetProjectDomainId()) > 0 { + candidates = append(candidates, ownerId.GetProjectDomainId()) + } + return candidates +} + +func ISharableMergeChangeOwnerCandidateDomainIds(model ISharableBaseModel, candidates ...[]string) []string { + var ret stringutils2.SSortedStrings + for i := range candidates { + if len(candidates[i]) > 0 { + cand := stringutils2.NewSortedStrings(candidates[i]) + ownerId := model.GetOwnerId() + if ownerId != nil && len(ownerId.GetProjectDomainId()) > 0 && !cand.Contains(ownerId.GetProjectDomainId()) { + cand = stringutils2.Append(cand, ownerId.GetProjectDomainId()) + } + if len(ret) > 0 { + ret = stringutils2.Intersect(ret, cand) + } else { + ret = stringutils2.NewSortedStrings(cand) + } + } + } + return ret +} + +func ISharableMergeShareRequireDomainIds(requiredIds ...[]string) []string { + var ret stringutils2.SSortedStrings + for i := range requiredIds { + if len(requiredIds[i]) > 0 { + req := stringutils2.NewSortedStrings(requiredIds[i]) + if ret == nil { + ret = req + } else { + ret = stringutils2.Merge(ret, req) + } + } else { + return nil + } + } + return ret } func SharableModelIsSharable(model ISharableBaseModel, reqUsrId mcclient.IIdentityProvider) bool { @@ -280,40 +336,52 @@ func SharablePerformPublic(model ISharableBaseModel, ctx context.Context, userCr return errors.Wrap(httperrors.ErrInputParameter, "cannot set shared_projects and shared_domains at the same time") } else if len(input.SharedProjects) > 0 && targetScope != rbacutils.ScopeProject { targetScope = rbacutils.ScopeProject - // return errors.Wrapf(httperrors.ErrInputParameter, "scope %s != project when shared_projects specified", targetScope) } else if len(input.SharedDomains) > 0 && targetScope != rbacutils.ScopeDomain { targetScope = rbacutils.ScopeDomain - // return errors.Wrapf(httperrors.ErrInputParameter, "scope %s != domain when shared_domains specified", targetScope) } shareResult := apis.PerformPublicInput{ Scope: string(targetScope), } + candidateIds := model.GetSharableTargetDomainIds() + requireIds := model.GetRequiredSharedDomainIds() + switch targetScope { case rbacutils.ScopeProject: + if len(requireIds) == 0 { + return errors.Wrap(httperrors.ErrForbidden, "require to be shared to system") + } else if len(requireIds) > 1 { + return errors.Wrap(httperrors.ErrForbidden, "require to be shared to other domain") + } if len(input.SharedProjects) == 0 { return errors.Wrap(httperrors.ErrEmptyRequest, "empty shared target project list") } - shareResult.SharedProjects, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetProject, input.SharedProjects) + shareResult.SharedProjects, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetProject, input.SharedProjects, nil, nil) if err != nil { return errors.Wrap(err, "shareToTarget") } case rbacutils.ScopeDomain: - _, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetProject, nil) + if len(requireIds) == 0 { + return errors.Wrap(httperrors.ErrForbidden, "require to be shared to system") + } + _, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetProject, nil, nil, nil) if err != nil { return errors.Wrap(err, "shareToTarget clean projects") } - shareResult.SharedDomains, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetDomain, input.SharedDomains) + shareResult.SharedDomains, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetDomain, input.SharedDomains, candidateIds, requireIds) if err != nil { return errors.Wrap(err, "shareToTarget add domains") } case rbacutils.ScopeSystem: - _, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetProject, nil) + if len(candidateIds) > 0 { + return errors.Wrapf(httperrors.ErrForbidden, "sharing is limited to domains %s", jsonutils.Marshal(candidateIds)) + } + _, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetProject, nil, nil, nil) if err != nil { return errors.Wrap(err, "shareToTarget clean projects") } - _, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetDomain, nil) + _, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetDomain, nil, nil, nil) if err != nil { return errors.Wrap(err, "shareToTarget clean domainss") } @@ -340,10 +408,17 @@ func SharablePerformPublic(model ISharableBaseModel, ctx context.Context, userCr } func SharablePerformPrivate(model ISharableBaseModel, ctx context.Context, userCred mcclient.TokenCredential) error { - if !model.GetIsPublic() { + if !model.GetIsPublic() && model.GetPublicScope() == rbacutils.ScopeNone { return nil } + requireIds := model.GetRequiredSharedDomainIds() + if len(requireIds) == 0 { + return errors.Wrap(httperrors.ErrForbidden, "require to be shared to system") + } else if len(requireIds) > 1 { + return errors.Wrap(httperrors.ErrForbidden, "require to be shared to other domain") + } + requireScope := model.GetPublicScope() allowScope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), model.GetModelManager().KeywordPlural(), policy.PolicyActionPerform, "private") if requireScope.HigherThan(allowScope) { diff --git a/pkg/cloudcommon/db/sharablebase_test.go b/pkg/cloudcommon/db/sharablebase_test.go new file mode 100644 index 0000000000..df9062e85a --- /dev/null +++ b/pkg/cloudcommon/db/sharablebase_test.go @@ -0,0 +1,68 @@ +package db + +import ( + "reflect" + "testing" +) + +func TestISharableMergeChangeOwnerCandidateDomainIds(t *testing.T) { + cases := []struct { + candidates [][]string + want []string + }{ + { + candidates: [][]string{ + nil, + []string{"abc"}, + []string{}, + []string{"abc", "bcd"}, + []string{"bcd"}, + }, + want: []string{"123"}, + }, + { + candidates: [][]string{ + nil, + []string{}, + }, + want: nil, + }, + } + model := &SInfrasResourceBase{} + model.DomainId = "123" + for _, c := range cases { + got := ISharableMergeChangeOwnerCandidateDomainIds(model, c.candidates...) + if !reflect.DeepEqual(got, c.want) { + t.Errorf("want: %s got %s", c.want, got) + } + } +} + +func TestISharableMergeShareRequireDomainIds(t *testing.T) { + cases := []struct { + requires [][]string + want []string + }{ + { + requires: [][]string{ + nil, + []string{"abc"}, + }, + want: nil, + }, + { + requires: [][]string{ + {"abc"}, + {"def"}, + {"abc", "def"}, + }, + want: []string{"abc", "def"}, + }, + } + for _, c := range cases { + got := ISharableMergeShareRequireDomainIds(c.requires...) + if !reflect.DeepEqual(got, c.want) { + t.Errorf("want: %s got %s", c.want, got) + } + } +} diff --git a/pkg/cloudcommon/db/sharablevirtual.go b/pkg/cloudcommon/db/sharablevirtual.go index 98a285055d..c71eef43b7 100644 --- a/pkg/cloudcommon/db/sharablevirtual.go +++ b/pkg/cloudcommon/db/sharablevirtual.go @@ -31,7 +31,7 @@ import ( type SSharableVirtualResourceBase struct { SVirtualResourceBase - SSharableBaseResource + SSharableBaseResource `"is_public=>create":"optional" "public_scope=>create":"optional"` // IsPublic bool `default:"false" nullable:"false" create:"domain_optional" list:"user" json:"is_public"` // PublicScope string `width:"16" charset:"ascii" nullable:"false" default:"system" create:"domain_optional" list:"user" json:"public_scope"` } @@ -70,7 +70,7 @@ func (model *SSharableVirtualResourceBase) AllowPerformPublic(ctx context.Contex } func (model *SSharableVirtualResourceBase) PerformPublic(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPublicInput) (jsonutils.JSONObject, error) { - err := SharablePerformPublic(model, ctx, userCred, input) + err := SharablePerformPublic(model.GetISharableVirtualModel(), ctx, userCred, input) if err != nil { return nil, errors.Wrap(err, "SharablePerformPublic") } @@ -82,7 +82,7 @@ func (model *SSharableVirtualResourceBase) AllowPerformPrivate(ctx context.Conte } func (model *SSharableVirtualResourceBase) PerformPrivate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPrivateInput) (jsonutils.JSONObject, error) { - err := SharablePerformPrivate(model, ctx, userCred) + err := SharablePerformPrivate(model.GetISharableVirtualModel(), ctx, userCred) if err != nil { return nil, errors.Wrap(err, "SharablePerformPrivate") } @@ -222,8 +222,8 @@ func (model *SSharableVirtualResourceBase) SyncShareState(ctx context.Context, u model.PublicScope = string(rbacutils.ScopeSystem) } else { model.PublicScope = string(rbacutils.ScopeDomain) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetProject, nil) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetDomain, shareInfo.SharedDomains) + SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetProject, nil, nil, nil) + SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetDomain, shareInfo.SharedDomains, nil, nil) } } return nil @@ -233,3 +233,11 @@ func (model *SSharableVirtualResourceBase) SyncShareState(ctx context.Context, u } } } + +func (model *SSharableVirtualResourceBase) GetSharableTargetDomainIds() []string { + return model.GetISharableVirtualModel().GetChangeOwnerCandidateDomainIds() +} + +func (model *SSharableVirtualResourceBase) GetRequiredSharedDomainIds() []string { + return []string{model.DomainId} +} diff --git a/pkg/cloudcommon/db/sharedresource.go b/pkg/cloudcommon/db/sharedresource.go index 7199ee4217..ff1ce86132 100644 --- a/pkg/cloudcommon/db/sharedresource.go +++ b/pkg/cloudcommon/db/sharedresource.go @@ -18,6 +18,8 @@ import ( "context" "database/sql" + "yunion.io/x/pkg/utils" + "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/cloudcommon/consts" @@ -69,16 +71,16 @@ func (manager *SSharedResourceManager) CleanModelShares(ctx context.Context, use resScope := model.GetModelManager().ResourceScope() switch resScope { case rbacutils.ScopeProject: - _, err = manager.shareToTarget(ctx, userCred, model, SharedTargetProject, nil) + _, err = manager.shareToTarget(ctx, userCred, model, SharedTargetProject, nil, nil, nil) if err != nil { return errors.Wrap(err, "remove shared project") } - _, err = manager.shareToTarget(ctx, userCred, model, SharedTargetDomain, nil) + _, err = manager.shareToTarget(ctx, userCred, model, SharedTargetDomain, nil, nil, nil) if err != nil { return errors.Wrap(err, "remove shared domain") } case rbacutils.ScopeDomain: - _, err = manager.shareToTarget(ctx, userCred, model, SharedTargetDomain, nil) + _, err = manager.shareToTarget(ctx, userCred, model, SharedTargetDomain, nil, nil, nil) if err != nil { return errors.Wrap(err, "remove shared domain") } @@ -92,6 +94,8 @@ func (manager *SSharedResourceManager) shareToTarget( model ISharableBaseModel, targetType string, targetIds []string, + candidateIds []string, + requireDomainIds []string, ) ([]string, error) { var requireScope rbacutils.TRbacScope resScope := model.GetModelManager().ResourceScope() @@ -159,7 +163,10 @@ func (manager *SSharedResourceManager) shareToTarget( return nil, errors.Wrapf(err, "fetch domain %s error", targetIds[i]) } if domain.GetId() == modelOwnerId.GetProjectDomainId() { - return nil, errors.Wrap(httperrors.ErrBadRequest, "can't share to self domain") + return nil, errors.Wrapf(httperrors.ErrBadRequest, "can't share to self domain %s", modelOwnerId.GetProjectDomainId()) + } + if len(candidateIds) > 0 && !utils.IsInStringArray(domain.GetId(), candidateIds) { + return nil, errors.Wrapf(httperrors.ErrForbidden, "share target domain %s not in candidate list %s", domain.GetId(), candidateIds) } newIds = stringutils2.Append(newIds, domain.GetId()) } @@ -170,6 +177,14 @@ func (manager *SSharedResourceManager) shareToTarget( return keepIds, nil } + if targetType == SharedTargetDomain && len(requireDomainIds) > 0 && len(delIds) > 0 { + for _, delId := range delIds { + if utils.IsInStringArray(delId, requireDomainIds) { + return nil, errors.Wrapf(httperrors.ErrForbidden, "domain %s is required to share", delId) + } + } + } + allowScope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), model.KeywordPlural(), policy.PolicyActionPerform, "public") if requireScope.HigherThan(allowScope) { return nil, errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require %s allow %s", requireScope, allowScope) diff --git a/pkg/cloudcommon/db/virtualresource.go b/pkg/cloudcommon/db/virtualresource.go index 1e27219b51..a4bff1bdb7 100644 --- a/pkg/cloudcommon/db/virtualresource.go +++ b/pkg/cloudcommon/db/virtualresource.go @@ -281,10 +281,9 @@ func (model *SVirtualResourceBase) PerformChangeOwner(ctx context.Context, userC var requireScope rbacutils.TRbacScope if ownerId.GetProjectDomainId() != model.DomainId { // change domain, do check - if managed, ok := model.GetIVirtualModel().(IManagedResoucceBase); ok { - if !managed.CanShareToDomain(ownerId.GetProjectDomainId()) { - return nil, errors.Wrap(httperrors.ErrForbidden, "cann't share across domain") - } + candidates := model.GetIVirtualModel().GetChangeOwnerCandidateDomainIds() + if len(candidates) > 0 && !utils.IsInStringArray(ownerId.GetProjectDomainId(), candidates) { + return nil, errors.Wrap(httperrors.ErrForbidden, "target domain not in change owner candidate list") } requireScope = rbacutils.ScopeSystem } else { @@ -531,3 +530,11 @@ func (model *SVirtualResourceBase) ValidateUpdateData( } return input, nil } + +func (model *SVirtualResourceBase) AllowGetDetailsChangeOwnerCandidateDomains(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return model.IsOwner(userCred) || IsAdminAllowGetSpec(userCred, model, "change-owner-candidate-domains") +} + +func (model *SVirtualResourceBase) GetDetailsChangeOwnerCandidateDomains(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (apis.ChangeOwnerCandidateDomainsOutput, error) { + return IOwnerResourceBaseModelGetChangeOwnerCandidateDomains(model.GetIVirtualModel()) +} diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index 51c7bdd29b..c1d07f2868 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -1960,18 +1960,28 @@ func (account *SCloudaccount) PerformPublic(ctx context.Context, userCred mcclie return nil, errors.Wrap(httperrors.ErrInvalidStatus, "cannot public in sync") } - if input.ShareMode != api.CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN && input.ShareMode != api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM { - return nil, errors.Wrap(httperrors.ErrInputParameter, "share_mode cannot be account_domain") - } - - if input.ShareMode == api.CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN { - providers := account.GetCloudproviders() - for i := range providers { - if !utils.IsInStringArray(providers[i].DomainId, input.SharedDomains) { - log.Warningf("provider's domainId %s is outside of list of shared domains", providers[i].DomainId) - input.SharedDomains = append(input.SharedDomains, providers[i].DomainId) + switch input.ShareMode { + case api.CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN: + if len(input.SharedDomains) == 0 { + input.Scope = string(rbacutils.ScopeSystem) + } else { + input.Scope = string(rbacutils.ScopeDomain) + providers := account.GetCloudproviders() + for i := range providers { + if !utils.IsInStringArray(providers[i].DomainId, input.SharedDomains) && providers[i].DomainId != account.DomainId { + log.Warningf("provider's domainId %s is outside of list of shared domains", providers[i].DomainId) + input.SharedDomains = append(input.SharedDomains, providers[i].DomainId) + } } } + case api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM: + if len(input.SharedDomains) == 0 { + input.Scope = string(rbacutils.ScopeSystem) + } else { + input.Scope = string(rbacutils.ScopeDomain) + } + default: + return nil, errors.Wrap(httperrors.ErrInputParameter, "share_mode cannot be account_domain") } _, err := account.SInfrasResourceBase.PerformPublic(ctx, userCred, query, input.PerformPublicInput) @@ -2095,14 +2105,14 @@ func (manager *SCloudaccountManager) filterByDomainId(q *sqlchemy.SQuery, domain ), // share_mode=system/public_scope=domain sqlchemy.AND( - sqlchemy.Equals(q.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM), + // sqlchemy.Equals(q.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM), sqlchemy.In(q.Field("id"), subq.SubQuery()), sqlchemy.IsTrue(q.Field("is_public")), sqlchemy.Equals(q.Field("public_scope"), rbacutils.ScopeDomain), ), // share_mode=system/public_scope=system sqlchemy.AND( - sqlchemy.Equals(q.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM), + // sqlchemy.Equals(q.Field("share_mode"), api.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM), sqlchemy.IsTrue(q.Field("is_public")), sqlchemy.Equals(q.Field("public_scope"), rbacutils.ScopeSystem), ), diff --git a/pkg/compute/models/globalvpcs.go b/pkg/compute/models/globalvpcs.go index 5e7066adc7..300fdd9ee0 100644 --- a/pkg/compute/models/globalvpcs.go +++ b/pkg/compute/models/globalvpcs.go @@ -218,3 +218,15 @@ func (globalVpc *SGlobalVpc) GetUsages() []db.IUsage { &usage, } } + +func (globalVpc *SGlobalVpc) GetRequiredSharedDomainIds() []string { + vpcs, _ := globalVpc.GetVpcs() + if len(vpcs) == 0 { + return globalVpc.SEnabledStatusInfrasResourceBase.GetRequiredSharedDomainIds() + } + requires := make([][]string, len(vpcs)) + for i := range vpcs { + requires[i] = db.ISharableChangeOwnerCandidateDomainIds(&vpcs[i]) + } + return db.ISharableMergeShareRequireDomainIds(requires...) +} diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index 9e584be506..8cc9677f22 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -4998,3 +4998,27 @@ func (host *SHost) GetUsages() []db.IUsage { &usage, } } + +func (host *SHost) PerformPublic(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPublicInput) (jsonutils.JSONObject, error) { + // perform public for all connected local storage + storages := host.GetAttachedLocalStorages() + for i := range storages { + _, err := storages[i].PerformPublic(ctx, userCred, query, input) + if err != nil { + return nil, errors.Wrap(err, "storage.PerformPublic") + } + } + return host.SEnabledStatusInfrasResourceBase.PerformPublic(ctx, userCred, query, input) +} + +func (host *SHost) PerformPrivate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPrivateInput) (jsonutils.JSONObject, error) { + // perform private for all connected local storage + storages := host.GetAttachedLocalStorages() + for i := range storages { + _, err := storages[i].PerformPrivate(ctx, userCred, query, input) + if err != nil { + return nil, errors.Wrap(err, "storage.PerformPrivate") + } + } + return host.SEnabledStatusInfrasResourceBase.PerformPrivate(ctx, userCred, query, input) +} diff --git a/pkg/compute/models/managedresource.go b/pkg/compute/models/managedresource.go index a699373884..c97ddc211f 100644 --- a/pkg/compute/models/managedresource.go +++ b/pkg/compute/models/managedresource.go @@ -348,15 +348,14 @@ func (manager *SManagedResourceBaseManager) GetOrderByFields(query api.ManagedRe return []string{query.OrderByManager, query.OrderByAccount, query.OrderByProvider, query.OrderByBrand} } -func (model *SManagedResourceBase) GetDetailsChangeOwnerCandidateDomains(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (api.ChangeOwnerCandidateDomainsOutput, error) { - output := api.ChangeOwnerCandidateDomainsOutput{} +func (model *SManagedResourceBase) GetChangeOwnerCandidateDomainIds() []string { provider := model.GetCloudprovider() if provider == nil { - return output, nil + return nil } account := model.GetCloudaccount() if account == nil { - return output, nil + return nil } var candidateIds []string switch account.ShareMode { @@ -370,19 +369,7 @@ func (model *SManagedResourceBase) GetDetailsChangeOwnerCandidateDomains(ctx con candidateIds = append(candidateIds, account.DomainId) } } - domainMap := make(map[string]db.STenant) - err := db.FetchQueryObjectsByIds(db.TenantCacheManager.GetDomainQuery(), "id", candidateIds, &domainMap) - if err != nil { - return output, errors.Wrap(err, "FetchQueryObjectsByIds") - } - output.Candidates = make([]apis.SharedDomain, len(candidateIds)) - for i := range candidateIds { - output.Candidates[i].Id = candidateIds[i] - if domain, ok := domainMap[candidateIds[i]]; ok { - output.Candidates[i].Name = domain.Name - } - } - return output, nil + return candidateIds } func _managedResourceFilterByDomain(managerIdFieldName string, q *sqlchemy.SQuery, query apis.DomainizedResourceListInput, filterField string, subqFunc func() *sqlchemy.SQuery) (*sqlchemy.SQuery, error) { diff --git a/pkg/compute/models/networks.go b/pkg/compute/models/networks.go index 1f6366c159..76a6ddf24a 100644 --- a/pkg/compute/models/networks.go +++ b/pkg/compute/models/networks.go @@ -2515,3 +2515,14 @@ func (net *SNetwork) PerformStatus(ctx context.Context, userCred mcclient.TokenC } return net.SSharableVirtualResourceBase.PerformStatus(ctx, userCred, query, input) } + +func (net *SNetwork) GetChangeOwnerCandidateDomainIds() []string { + candidates := [][]string{ + net.SSharableVirtualResourceBase.GetChangeOwnerCandidateDomainIds(), + } + wire := net.GetWire() + if wire != nil { + candidates = append(candidates, db.ISharableChangeOwnerCandidateDomainIds(wire)) + } + return db.ISharableMergeChangeOwnerCandidateDomainIds(net, candidates...) +} diff --git a/pkg/compute/models/storages.go b/pkg/compute/models/storages.go index e26dcd336e..bd7354766e 100644 --- a/pkg/compute/models/storages.go +++ b/pkg/compute/models/storages.go @@ -394,7 +394,7 @@ func (self *SStorage) GetSnapshotCount() (int, error) { } func (self *SStorage) IsLocal() bool { - return self.StorageType == api.STORAGE_LOCAL || self.StorageType == api.STORAGE_BAREMETAL + return utils.IsInStringArray(self.StorageType, api.HOST_STORAGE_LOCAL_TYPES) } func (self *SStorage) GetStorageCachePath(mountPoint, imageCachePath string) string { @@ -1456,3 +1456,25 @@ func (self *SStorage) StartDeleteRbdDisks(ctx context.Context, userCred mcclient task.ScheduleRun(nil) return nil } + +func (storage *SStorage) PerformPublic(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPublicInput) (jsonutils.JSONObject, error) { + // not allow to perform public for locally connected storage + if storage.IsLocal() { + hosts := storage.GetAttachedHosts() + if len(hosts) > 0 { + return nil, errors.Wrap(httperrors.ErrForbidden, "not allow to perform public for local storage") + } + } + return storage.SEnabledStatusInfrasResourceBase.PerformPublic(ctx, userCred, query, input) +} + +func (storage *SStorage) PerformPrivate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPrivateInput) (jsonutils.JSONObject, error) { + // not allow to perform private for locally conencted storage + if storage.IsLocal() { + hosts := storage.GetAttachedHosts() + if len(hosts) > 0 { + return nil, errors.Wrap(httperrors.ErrForbidden, "not allow to perform private for local storage") + } + } + return storage.SEnabledStatusInfrasResourceBase.PerformPrivate(ctx, userCred, query, input) +} diff --git a/pkg/compute/models/vpcs.go b/pkg/compute/models/vpcs.go index 26f977a1c9..9e46f23147 100644 --- a/pkg/compute/models/vpcs.go +++ b/pkg/compute/models/vpcs.go @@ -576,6 +576,7 @@ func (manager *SVpcManager) InitializeData() error { defVpc.Description = "Default VPC" defVpc.Status = api.VPC_STATUS_AVAILABLE defVpc.IsDefault = true + defVpc.PublicScope = string(rbacutils.ScopeSystem) err = manager.TableSpec().Insert(&defVpc) if err != nil { log.Errorf("Insert default vpc fail: %s", err) @@ -586,9 +587,10 @@ func (manager *SVpcManager) InitializeData() error { } } else { vpc := vpcObj.(*SVpc) - if vpc.Status != api.VPC_STATUS_AVAILABLE { + if vpc.Status != api.VPC_STATUS_AVAILABLE || (vpc.PublicScope == string(rbacutils.ScopeSystem) && !vpc.IsPublic) { _, err = db.Update(vpc, func() error { vpc.Status = api.VPC_STATUS_AVAILABLE + vpc.IsPublic = true return nil }) return err @@ -1091,3 +1093,26 @@ func (manager *SVpcManager) totalCount( return cnt } + +func (vpc *SVpc) GetChangeOwnerCandidateDomainIds() []string { + candidates := [][]string{ + vpc.SEnabledStatusInfrasResourceBase.GetChangeOwnerCandidateDomainIds(), + } + globalVpc, _ := vpc.GetGlobalVpc() + if globalVpc != nil { + candidates = append(candidates, db.ISharableChangeOwnerCandidateDomainIds(globalVpc)) + } + return db.ISharableMergeChangeOwnerCandidateDomainIds(vpc, candidates...) +} + +func (vpc *SVpc) GetRequiredSharedDomainIds() []string { + wires := vpc.GetWires() + if len(wires) == 0 { + return vpc.SEnabledStatusInfrasResourceBase.GetRequiredSharedDomainIds() + } + requires := make([][]string, len(wires)) + for i := range wires { + requires[i] = db.ISharableChangeOwnerCandidateDomainIds(&wires[i]) + } + return db.ISharableMergeShareRequireDomainIds(requires...) +} diff --git a/pkg/compute/models/wires.go b/pkg/compute/models/wires.go index 616429c154..168e955b37 100644 --- a/pkg/compute/models/wires.go +++ b/pkg/compute/models/wires.go @@ -998,3 +998,26 @@ func (model *SWire) CustomizeCreate(ctx context.Context, userCred mcclient.Token model.PublicScope = string(rbacutils.ScopeSystem) return model.SInfrasResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) } + +func (wire *SWire) GetChangeOwnerCandidateDomainIds() []string { + candidates := [][]string{ + wire.SInfrasResourceBase.GetChangeOwnerCandidateDomainIds(), + } + vpc := wire.GetVpc() + if vpc != nil { + candidates = append(candidates, db.ISharableChangeOwnerCandidateDomainIds(vpc)) + } + return db.ISharableMergeChangeOwnerCandidateDomainIds(wire, candidates...) +} + +func (wire *SWire) GetRequiredSharedDomainIds() []string { + networks, _ := wire.getNetworks() + if len(networks) == 0 { + return wire.SInfrasResourceBase.GetRequiredSharedDomainIds() + } + requires := make([][]string, len(networks)) + for i := range networks { + requires[i] = db.ISharableChangeOwnerCandidateDomainIds(&networks[i]) + } + return db.ISharableMergeShareRequireDomainIds(requires...) +} diff --git a/pkg/image/models/images.go b/pkg/image/models/images.go index 41dfb75c9e..f7b2beff1e 100644 --- a/pkg/image/models/images.go +++ b/pkg/image/models/images.go @@ -1441,3 +1441,23 @@ func (img *SImage) GetUsages() []db.IUsage { &usage, } } + +func (img *SImage) PerformPublic(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPublicInput) (jsonutils.JSONObject, error) { + if img.IsStandard.IsTrue() { + return nil, errors.Wrap(httperrors.ErrForbidden, "cannot perform public for standard image") + } + if img.IsGuestImage.IsTrue() { + return nil, errors.Wrap(httperrors.ErrForbidden, "cannot perform public for guest image") + } + return img.SSharableVirtualResourceBase.PerformPublic(ctx, userCred, query, input) +} + +func (img *SImage) PerformPrivate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPrivateInput) (jsonutils.JSONObject, error) { + if img.IsStandard.IsTrue() { + return nil, errors.Wrap(httperrors.ErrForbidden, "cannot perform private for standard image") + } + if img.IsGuestImage.IsTrue() { + return nil, errors.Wrap(httperrors.ErrForbidden, "cannot perform private for guest image") + } + return img.SSharableVirtualResourceBase.PerformPrivate(ctx, userCred, query, input) +} diff --git a/pkg/keystone/models/policies.go b/pkg/keystone/models/policies.go index 9ae307a3fd..a6f745980e 100644 --- a/pkg/keystone/models/policies.go +++ b/pkg/keystone/models/policies.go @@ -308,9 +308,11 @@ func (manager *SPolicyManager) FetchCustomizeColumns( ) []api.PolicyDetails { rows := make([]api.PolicyDetails, len(objs)) identRows := manager.SEnabledIdentityBaseResourceManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + shareRows := manager.SSharableBaseResourceManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) for i := range rows { rows[i] = api.PolicyDetails{ EnabledIdentityBaseResourceDetails: identRows[i], + SharableResourceBaseInfo: shareRows[i], } } return rows @@ -330,3 +332,15 @@ func (policy *SPolicy) GetUsages() []db.IUsage { func (manager *SPolicyManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery { return db.SharableManagerFilterByOwner(manager, q, owner, scope) } + +func (policy *SPolicy) GetSharableTargetDomainIds() []string { + return nil +} + +func (policy *SPolicy) GetRequiredSharedDomainIds() []string { + return []string{policy.DomainId} +} + +func (policy *SPolicy) GetSharedDomains() []string { + return db.SharableGetSharedProjects(policy, db.SharedTargetDomain) +} diff --git a/pkg/keystone/models/roles.go b/pkg/keystone/models/roles.go index 1f32cab5cd..af51750cf3 100644 --- a/pkg/keystone/models/roles.go +++ b/pkg/keystone/models/roles.go @@ -225,10 +225,12 @@ func (manager *SRoleManager) FetchCustomizeColumns( rows := make([]api.RoleDetails, len(objs)) identRows := manager.SIdentityBaseResourceManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + shareRows := manager.SSharableBaseResourceManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) for i := range rows { rows[i] = api.RoleDetails{ IdentityBaseResourceDetails: identRows[i], + SharableResourceBaseInfo: shareRows[i], } role := objs[i].(*SRole) rows[i].UserCount, _ = role.GetUserCount() @@ -515,3 +517,15 @@ func (role *SRole) PostCreate( func (manager *SRoleManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery { return db.SharableManagerFilterByOwner(manager, q, owner, scope) } + +func (role *SRole) GetSharableTargetDomainIds() []string { + return nil +} + +func (role *SRole) GetRequiredSharedDomainIds() []string { + return []string{role.DomainId} +} + +func (role *SRole) GetSharedDomains() []string { + return db.SharableGetSharedProjects(role, db.SharedTargetDomain) +} diff --git a/pkg/mcclient/modules/mod_buckets.go b/pkg/mcclient/modules/mod_buckets.go index 5cbcf5dd9f..f26263fe94 100644 --- a/pkg/mcclient/modules/mod_buckets.go +++ b/pkg/mcclient/modules/mod_buckets.go @@ -73,7 +73,7 @@ func init() { NewComputeManager("bucket", "buckets", []string{"ID", "Name", "Storage_Class", "Status", "location", "acl", - "region", "manager_id", + "region", "manager_id", "public_scope", }, []string{}), } diff --git a/pkg/mcclient/modules/mod_cloudaccounts.go b/pkg/mcclient/modules/mod_cloudaccounts.go index 77c9e834fe..b277905ae5 100644 --- a/pkg/mcclient/modules/mod_cloudaccounts.go +++ b/pkg/mcclient/modules/mod_cloudaccounts.go @@ -28,7 +28,8 @@ func init() { "guest_count", "project_domain", "domain_id", "Provider", "Brand", "Enable_Auto_Sync", "Sync_Interval_Seconds", - "Share_Mode"}, + "Share_Mode", "is_public", "public_scope", + }, []string{}) registerCompute(&Cloudaccounts) diff --git a/pkg/mcclient/modules/mod_globalvpcs.go b/pkg/mcclient/modules/mod_globalvpcs.go index 95de9ec398..a1d8a9924e 100644 --- a/pkg/mcclient/modules/mod_globalvpcs.go +++ b/pkg/mcclient/modules/mod_globalvpcs.go @@ -29,7 +29,7 @@ var ( func init() { GlobalVpcs = GlobalVpcManager{NewComputeManager("globalvpc", "globalvpcs", []string{}, - []string{"ID", "Name", "Description", "Status", "Enabled"})} + []string{"ID", "Name", "Description", "Status", "Enabled", "public_scope"})} registerCompute(&GlobalVpcs) } diff --git a/pkg/mcclient/modules/mod_hosts.go b/pkg/mcclient/modules/mod_hosts.go index 3a132f6435..54ab6f9559 100644 --- a/pkg/mcclient/modules/mod_hosts.go +++ b/pkg/mcclient/modules/mod_hosts.go @@ -185,6 +185,7 @@ func init() { "storage_size", "expired_at", "domain_id", "project_domain", + "public_scope", }, []string{})} registerCompute(&Hosts) diff --git a/pkg/mcclient/modules/mod_policies.go b/pkg/mcclient/modules/mod_policies.go index cc6f3693ae..e5f680f4eb 100644 --- a/pkg/mcclient/modules/mod_policies.go +++ b/pkg/mcclient/modules/mod_policies.go @@ -30,7 +30,7 @@ var Policies SPolicyManager func policyReadFilter(session *mcclient.ClientSession, s jsonutils.JSONObject, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { ss := s.(*jsonutils.JSONDict) - ret := ss.CopyIncludes("id", "type", "enabled", "domain_id", "domain", "project_domain", "can_update", "can_delete", "is_public", "description", "delete_fail_reason", "update_fail_reason") + ret := ss.CopyIncludes("id", "type", "enabled", "domain_id", "domain", "project_domain", "can_update", "can_delete", "is_public", "description", "delete_fail_reason", "update_fail_reason", "public_scope", "shared_domains") blobJson, _ := ss.Get("blob") if blobJson != nil { policy := rbacutils.SRbacPolicy{} @@ -82,7 +82,7 @@ func policyWriteFilter(session *mcclient.ClientSession, s jsonutils.JSONObject, ret.Add(blobJson, "blob") } for _, k := range []string{ - "type", "enabled", "domain", "domain_id", "project_domain", "description", + "type", "enabled", "domain", "domain_id", "project_domain", "description", "is_public", "public_scope", "shared_domains", } { if s.Contains(k) { val, err := s.Get(k) diff --git a/pkg/mcclient/modules/mod_storages.go b/pkg/mcclient/modules/mod_storages.go index 40b2a6627a..0d59d366ba 100644 --- a/pkg/mcclient/modules/mod_storages.go +++ b/pkg/mcclient/modules/mod_storages.go @@ -22,7 +22,7 @@ var ( func init() { Storages = NewComputeManager("storage", "storages", - []string{"ID", "Name", "Capacity", "Status", "Used_capacity", "Waste_capacity", "Free_capacity", "Storage_type", "Medium_type", "Virtual_capacity", "commit_bound", "commit_rate", "Enabled"}, + []string{"ID", "Name", "Capacity", "Status", "Used_capacity", "Waste_capacity", "Free_capacity", "Storage_type", "Medium_type", "Virtual_capacity", "commit_bound", "commit_rate", "Enabled", "public_scope"}, []string{}) registerCompute(&Storages) diff --git a/pkg/mcclient/modules/mod_vpcs.go b/pkg/mcclient/modules/mod_vpcs.go index 006bac35b0..46f501191e 100644 --- a/pkg/mcclient/modules/mod_vpcs.go +++ b/pkg/mcclient/modules/mod_vpcs.go @@ -22,7 +22,7 @@ var ( func init() { Vpcs = NewComputeManager("vpc", "vpcs", - []string{"ID", "Name", "Enabled", "Status", "Cloudregion_Id", "Is_default", "Cidr_Block", "Region"}, + []string{"ID", "Name", "Enabled", "Status", "Cloudregion_Id", "Is_default", "Cidr_Block", "Region", "Public_Scope"}, []string{}) registerCompute(&Vpcs) diff --git a/pkg/mcclient/modules/mod_wires.go b/pkg/mcclient/modules/mod_wires.go index 0eedfffb20..e9ab755306 100644 --- a/pkg/mcclient/modules/mod_wires.go +++ b/pkg/mcclient/modules/mod_wires.go @@ -23,7 +23,7 @@ var ( func init() { Wires = NewComputeManager("wire", "wires", []string{"ID", "Name", "Bandwidth", "Zone_ID", - "Zone", "Networks", "VPC", "VPC_ID"}, + "Zone", "Networks", "VPC", "VPC_ID", "public_scope"}, []string{}) registerCompute(&Wires) diff --git a/pkg/util/stringutils2/sortedstrings.go b/pkg/util/stringutils2/sortedstrings.go index 9c7954ace4..1224b4d8e2 100644 --- a/pkg/util/stringutils2/sortedstrings.go +++ b/pkg/util/stringutils2/sortedstrings.go @@ -161,3 +161,21 @@ func Merge(a, b SSortedStrings) SSortedStrings { } return SSortedStrings(ret) } + +func Intersect(a, b SSortedStrings) SSortedStrings { + ret := make([]string, 0) + i := 0 + j := 0 + for i < len(a) && j < len(b) { + if a[i] == b[j] { + ret = append(ret, a[i]) + i += 1 + j += 1 + } else if a[i] < b[j] { + i += 1 + } else if a[i] > b[j] { + j += 1 + } + } + return SSortedStrings(ret) +}