diff --git a/cmd/climc/shell/cloudid/cloudgroup.go b/cmd/climc/shell/cloudid/cloudgroup.go index 71df5c8d60..31eb51cdd5 100644 --- a/cmd/climc/shell/cloudid/cloudgroup.go +++ b/cmd/climc/shell/cloudid/cloudgroup.go @@ -17,22 +17,22 @@ package cloudid import ( "yunion.io/x/onecloud/cmd/climc/shell" "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/cloudid" ) func init() { cmd := shell.NewResourceCmd(&modules.Cloudgroups).WithKeyword("cloud-group") - cmd.List(&options.CloudgroupListOptions{}) - cmd.Create(&options.CloudgroupCreateOptions{}) - cmd.Show(&options.CloudgroupIdOptions{}) - cmd.Delete(&options.CloudgroupIdOptions{}) - cmd.Perform("syncstatus", &options.CloudgroupIdOptions{}) - cmd.Perform("attach-policy", &options.CloudgroupPolicyOptions{}) - cmd.Perform("detach-policy", &options.CloudgroupPolicyOptions{}) - cmd.Perform("add-user", &options.CloudgroupUserOptions{}) - cmd.Perform("remove-user", &options.CloudgroupUserOptions{}) - cmd.Perform("set-policies", &options.CloudgroupPolicyOptions{}) - cmd.Perform("set-users", &options.CloudgroupUserOptions{}) - cmd.Perform("public", &options.CloudgroupPublicOptions{}) - cmd.Perform("private", &options.CloudgroupIdOptions{}) + cmd.List(&cloudid.CloudgroupListOptions{}) + cmd.Create(&cloudid.CloudgroupCreateOptions{}) + cmd.Show(&cloudid.CloudgroupIdOptions{}) + cmd.Delete(&cloudid.CloudgroupIdOptions{}) + cmd.Perform("syncstatus", &cloudid.CloudgroupIdOptions{}) + cmd.Perform("attach-policy", &cloudid.CloudgroupPolicyOptions{}) + cmd.Perform("detach-policy", &cloudid.CloudgroupPolicyOptions{}) + cmd.Perform("add-user", &cloudid.CloudgroupUserOptions{}) + cmd.Perform("remove-user", &cloudid.CloudgroupUserOptions{}) + cmd.Perform("set-policies", &cloudid.CloudgroupPolicyOptions{}) + cmd.Perform("set-users", &cloudid.CloudgroupUserOptions{}) + cmd.Perform("public", &cloudid.CloudgroupPublicOptions{}) + cmd.Perform("private", &cloudid.CloudgroupIdOptions{}) } diff --git a/cmd/climc/shell/cloudid/cloudgroupcache.go b/cmd/climc/shell/cloudid/cloudgroupcache.go index 3323d9e07e..6adf684ce2 100644 --- a/cmd/climc/shell/cloudid/cloudgroupcache.go +++ b/cmd/climc/shell/cloudid/cloudgroupcache.go @@ -15,59 +15,15 @@ package cloudid import ( - "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/cmd/climc/shell" "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/cloudid" ) func init() { - type CloudgroupcacheListOptions struct { - options.BaseListOptions - - CloudgroupId string `json:"cloudgroup_id"` - } - R(&CloudgroupcacheListOptions{}, "cloud-group-cache-list", "List cloud group caches", func(s *mcclient.ClientSession, opts *CloudgroupcacheListOptions) error { - params, err := options.ListStructToParams(opts) - if err != nil { - return err - } - result, err := modules.Cloudgroupcaches.List(s, params) - if err != nil { - return err - } - printList(result, modules.Cloudgroupcaches.GetColumns(s)) - return nil - }) - - type CloudgroupcacheIdOption struct { - ID string `help:"Cloudgroup Id"` - } - - R(&CloudgroupcacheIdOption{}, "cloud-group-cache-show", "Show cloud groupcache details", func(s *mcclient.ClientSession, opts *CloudgroupcacheIdOption) error { - result, err := modules.Cloudgroupcaches.Get(s, opts.ID, nil) - if err != nil { - return err - } - printObject(result) - return nil - }) - - R(&CloudgroupcacheIdOption{}, "cloud-group-cache-syncstatus", "Sync cloudgroupcache", func(s *mcclient.ClientSession, opts *CloudgroupcacheIdOption) error { - result, err := modules.Cloudgroupcaches.PerformAction(s, opts.ID, "syncstatus", nil) - if err != nil { - return err - } - printObject(result) - return nil - }) - - R(&CloudgroupcacheIdOption{}, "cloud-group-cache-delete", "Delete cloudgroupcache", func(s *mcclient.ClientSession, opts *CloudgroupcacheIdOption) error { - result, err := modules.Cloudgroupcaches.Delete(s, opts.ID, nil) - if err != nil { - return err - } - printObject(result) - return nil - }) - + cmd := shell.NewResourceCmd(&modules.Cloudgroupcaches).WithKeyword("cloud-group-cache") + cmd.List(&cloudid.CloudgroupcacheListOptions{}) + cmd.Show(&cloudid.CloudgroupcacheIdOption{}) + cmd.Delete(&cloudid.CloudgroupcacheIdOption{}) + cmd.Perform("syncstatus", &cloudid.CloudgroupcacheIdOption{}) } diff --git a/cmd/climc/shell/cloudid/cloudgrouppolicies.go b/cmd/climc/shell/cloudid/cloudgrouppolicies.go index aea89de86b..9439323ba0 100644 --- a/cmd/climc/shell/cloudid/cloudgrouppolicies.go +++ b/cmd/climc/shell/cloudid/cloudgrouppolicies.go @@ -15,58 +15,12 @@ package cloudid import ( - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/cmd/climc/shell" "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/cloudid" ) func init() { - type CloudgroupPolicyListOptions struct { - options.BaseListOptions - Cloudgroup string `help:"ID or Name of Cloudgroup"` - Cloudpolicy string `help:"Policy ID or name"` - } - R(&CloudgroupPolicyListOptions{}, "cloud-group-policy-list", "List cloudgroup cloudpolicy pairs", func(s *mcclient.ClientSession, args *CloudgroupPolicyListOptions) error { - var params *jsonutils.JSONDict - { - var err error - params, err = args.BaseListOptions.Params() - if err != nil { - return err - - } - } - var result *modulebase.ListResult - var err error - if len(args.Cloudgroup) > 0 { - result, err = modules.Cloudgrouppolicies.ListDescendent(s, args.Cloudgroup, params) - } else if len(args.Cloudpolicy) > 0 { - result, err = modules.Cloudgrouppolicies.ListDescendent2(s, args.Cloudpolicy, params) - } else { - result, err = modules.Cloudgrouppolicies.List(s, params) - } - if err != nil { - return err - } - printList(result, modules.Cloudgrouppolicies.GetColumns(s)) - return nil - }) - - type CloudgroupPolicyDetailOptions struct { - CLOUDUSER string `help:"ID or Name of Cloudgroup"` - CLOUDPOLICY string `help:"ID or Name of Cloudpolicy"` - } - R(&CloudgroupPolicyDetailOptions{}, "cloud-group-policy-show", "Show cloudgrouppolicy details", func(s *mcclient.ClientSession, args *CloudgroupPolicyDetailOptions) error { - query := jsonutils.NewDict() - result, err := modules.Cloudgrouppolicies.Get(s, args.CLOUDUSER, args.CLOUDPOLICY, query) - if err != nil { - return err - } - printObject(result) - return nil - }) - + cmd := shell.NewResourceCmd(&modules.Cloudgrouppolicies).WithKeyword("cloud-group-policy").WithContextManager(&modules.Cloudgroups) + cmd.List(&cloudid.CloudgroupPolicyListOptions{}) } diff --git a/cmd/climc/shell/cloudid/cloudgroupusers.go b/cmd/climc/shell/cloudid/cloudgroupusers.go index 30d141789e..e17fb0f14e 100644 --- a/cmd/climc/shell/cloudid/cloudgroupusers.go +++ b/cmd/climc/shell/cloudid/cloudgroupusers.go @@ -15,58 +15,12 @@ package cloudid import ( - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/cmd/climc/shell" "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/cloudid" ) func init() { - type CloudgroupUserListOptions struct { - options.BaseListOptions - Cloudgroup string `help:"ID or Name of Cloudgroup"` - Clouduser string `help:"User ID or name"` - } - R(&CloudgroupUserListOptions{}, "cloud-group-user-list", "List cloudgroup clouduser pairs", func(s *mcclient.ClientSession, args *CloudgroupUserListOptions) error { - var params *jsonutils.JSONDict - { - var err error - params, err = args.BaseListOptions.Params() - if err != nil { - return err - - } - } - var result *modulebase.ListResult - var err error - if len(args.Cloudgroup) > 0 { - result, err = modules.Cloudgroupusers.ListDescendent(s, args.Cloudgroup, params) - } else if len(args.Clouduser) > 0 { - result, err = modules.Cloudgroupusers.ListDescendent2(s, args.Clouduser, params) - } else { - result, err = modules.Cloudgroupusers.List(s, params) - } - if err != nil { - return err - } - printList(result, modules.Cloudgroupusers.GetColumns(s)) - return nil - }) - - type CloudgroupUserDetailOptions struct { - CLOUDUSER string `help:"ID or Name of Cloudgroup"` - CLOUDPOLICY string `help:"ID or Name of Clouduser"` - } - R(&CloudgroupUserDetailOptions{}, "cloud-group-user-show", "Show cloudgroupuser details", func(s *mcclient.ClientSession, args *CloudgroupUserDetailOptions) error { - query := jsonutils.NewDict() - result, err := modules.Cloudgroupusers.Get(s, args.CLOUDUSER, args.CLOUDPOLICY, query) - if err != nil { - return err - } - printObject(result) - return nil - }) - + cmd := shell.NewResourceCmd(&modules.Cloudgroupusers).WithKeyword("cloud-group-user").WithContextManager(&modules.Cloudusers) + cmd.List(&cloudid.CloudgroupUserListOptions{}) } diff --git a/cmd/climc/shell/cloudid/cloudpolicy.go b/cmd/climc/shell/cloudid/cloudpolicy.go index 3f3d9825c2..4531d1d706 100644 --- a/cmd/climc/shell/cloudid/cloudpolicy.go +++ b/cmd/climc/shell/cloudid/cloudpolicy.go @@ -17,18 +17,18 @@ package cloudid import ( "yunion.io/x/onecloud/cmd/climc/shell" "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/cloudid" ) func init() { cmd := shell.NewResourceCmd(&modules.Cloudpolicies).WithKeyword("cloud-policy") - cmd.Create(&options.CloudpolicyListOptions{}) - cmd.List(&options.CloudpolicyListOptions{}) - cmd.Show(&options.CloudpolicyIdOptions{}) - cmd.Update(&options.CloudpolicyUpdateOption{}) - cmd.Perform("syncstauts", &options.CloudpolicyIdOptions{}) - cmd.Perform("lock", &options.CloudpolicyIdOptions{}) - cmd.Perform("unlock", &options.CloudpolicyIdOptions{}) - cmd.Perform("assign-group", &options.CloudpolicyGroupOptions{}) - cmd.Perform("revoke-group", &options.CloudpolicyGroupOptions{}) + cmd.Create(&cloudid.CloudpolicyListOptions{}) + cmd.List(&cloudid.CloudpolicyListOptions{}) + cmd.Show(&cloudid.CloudpolicyIdOptions{}) + cmd.Update(&cloudid.CloudpolicyUpdateOption{}) + cmd.Perform("syncstauts", &cloudid.CloudpolicyIdOptions{}) + cmd.Perform("lock", &cloudid.CloudpolicyIdOptions{}) + cmd.Perform("unlock", &cloudid.CloudpolicyIdOptions{}) + cmd.Perform("assign-group", &cloudid.CloudpolicyGroupOptions{}) + cmd.Perform("revoke-group", &cloudid.CloudpolicyGroupOptions{}) } diff --git a/cmd/climc/shell/cloudid/cloudpolicycache.go b/cmd/climc/shell/cloudid/cloudpolicycache.go index dac442fab0..50d9fdf826 100644 --- a/cmd/climc/shell/cloudid/cloudpolicycache.go +++ b/cmd/climc/shell/cloudid/cloudpolicycache.go @@ -15,28 +15,12 @@ package cloudid import ( - "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/cmd/climc/shell" "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/cloudid" ) func init() { - type CloudpolicycacheListOptions struct { - options.BaseListOptions - - CloudpolyId string - CloudaccountId string - } - R(&CloudpolicycacheListOptions{}, "cloud-policy-cache-list", "List cloud policy caches", func(s *mcclient.ClientSession, opts *CloudpolicycacheListOptions) error { - params, err := options.ListStructToParams(opts) - if err != nil { - return err - } - result, err := modules.Cloudpolicycaches.List(s, params) - if err != nil { - return err - } - printList(result, modules.Cloudpolicycaches.GetColumns(s)) - return nil - }) + cmd := shell.NewResourceCmd(&modules.Cloudpolicycaches).WithKeyword("cloud-policy-cache") + cmd.List(&cloudid.CloudpolicycacheListOptions{}) } diff --git a/cmd/climc/shell/cloudid/cloudrole.go b/cmd/climc/shell/cloudid/cloudrole.go new file mode 100644 index 0000000000..ce96377d42 --- /dev/null +++ b/cmd/climc/shell/cloudid/cloudrole.go @@ -0,0 +1,29 @@ +// 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 cloudid + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options/cloudid" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.Cloudroles).WithKeyword("cloud-role") + cmd.List(&cloudid.CloudroleListOptions{}) + cmd.Delete(&cloudid.CloudroleIdOptions{}) + cmd.Show(&cloudid.CloudroleIdOptions{}) + cmd.Perform("purge", &cloudid.CloudroleIdOptions{}) +} diff --git a/cmd/climc/shell/cloudid/clouduserpolicies.go b/cmd/climc/shell/cloudid/clouduserpolicies.go index c7a98f7acf..176c9e73d7 100644 --- a/cmd/climc/shell/cloudid/clouduserpolicies.go +++ b/cmd/climc/shell/cloudid/clouduserpolicies.go @@ -15,58 +15,12 @@ package cloudid import ( - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/cmd/climc/shell" "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/cloudid" ) func init() { - type ClouduserPolicyListOptions struct { - options.BaseListOptions - Clouduser string `help:"ID or Name of Clouduser"` - Cloudpolicy string `help:"Policy ID or name"` - } - R(&ClouduserPolicyListOptions{}, "cloud-user-policy-list", "List clouduser cloudpolicy pairs", func(s *mcclient.ClientSession, args *ClouduserPolicyListOptions) error { - var params *jsonutils.JSONDict - { - var err error - params, err = args.BaseListOptions.Params() - if err != nil { - return err - - } - } - var result *modulebase.ListResult - var err error - if len(args.Clouduser) > 0 { - result, err = modules.Clouduserpolicies.ListDescendent(s, args.Clouduser, params) - } else if len(args.Cloudpolicy) > 0 { - result, err = modules.Clouduserpolicies.ListDescendent2(s, args.Cloudpolicy, params) - } else { - result, err = modules.Clouduserpolicies.List(s, params) - } - if err != nil { - return err - } - printList(result, modules.Clouduserpolicies.GetColumns(s)) - return nil - }) - - type ClouduserPolicyDetailOptions struct { - CLOUDUSER string `help:"ID or Name of Clouduser"` - CLOUDPOLICY string `help:"ID or Name of Cloudpolicy"` - } - R(&ClouduserPolicyDetailOptions{}, "cloud-user-policy-show", "Show clouduserpolicy details", func(s *mcclient.ClientSession, args *ClouduserPolicyDetailOptions) error { - query := jsonutils.NewDict() - result, err := modules.Clouduserpolicies.Get(s, args.CLOUDUSER, args.CLOUDPOLICY, query) - if err != nil { - return err - } - printObject(result) - return nil - }) - + cmd := shell.NewResourceCmd(&modules.Clouduserpolicies).WithKeyword("cloud-user-policy").WithContextManager(&modules.Cloudpolicies) + cmd.List(&cloudid.ClouduserPolicyListOptions{}) } diff --git a/cmd/climc/shell/cloudid/cloudusers.go b/cmd/climc/shell/cloudid/cloudusers.go index 27893882b2..efa561f017 100644 --- a/cmd/climc/shell/cloudid/cloudusers.go +++ b/cmd/climc/shell/cloudid/cloudusers.go @@ -17,22 +17,22 @@ package cloudid import ( "yunion.io/x/onecloud/cmd/climc/shell" "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/cloudid" ) func init() { cmd := shell.NewResourceCmd(&modules.Cloudusers).WithKeyword("cloud-user") - cmd.List(&options.ClouduserListOptions{}) - cmd.Create(&options.ClouduserCreateOptions{}) - cmd.Show(&options.ClouduserIdOption{}) - cmd.Custom(shell.CustomActionGet, "login-info", &options.ClouduserIdOption{}) - cmd.Delete(&options.ClouduserIdOption{}) - cmd.Perform("sync", &options.ClouduserSyncOptions{}) - cmd.Perform("syncstatus", &options.ClouduserIdOption{}) - cmd.Perform("attach-policy", &options.ClouduserPolicyOptions{}) - cmd.Perform("detach-policy", &options.ClouduserPolicyOptions{}) - cmd.Perform("change-owner", &options.ClouduserChangeOwnerOptions{}) - cmd.Perform("reset-password", &options.ClouduserResetPasswordOptions{}) - cmd.Perform("cloud-user-join-group", &options.ClouduserGroupOptions{}) - cmd.Perform("cloud-user-leave-group", &options.ClouduserGroupOptions{}) + cmd.List(&cloudid.ClouduserListOptions{}) + cmd.Create(&cloudid.ClouduserCreateOptions{}) + cmd.Show(&cloudid.ClouduserIdOption{}) + cmd.Custom(shell.CustomActionGet, "login-info", &cloudid.ClouduserIdOption{}) + cmd.Delete(&cloudid.ClouduserIdOption{}) + cmd.Perform("sync", &cloudid.ClouduserSyncOptions{}) + cmd.Perform("syncstatus", &cloudid.ClouduserIdOption{}) + cmd.Perform("attach-policy", &cloudid.ClouduserPolicyOptions{}) + cmd.Perform("detach-policy", &cloudid.ClouduserPolicyOptions{}) + cmd.Perform("change-owner", &cloudid.ClouduserChangeOwnerOptions{}) + cmd.Perform("reset-password", &cloudid.ClouduserResetPasswordOptions{}) + cmd.Perform("cloud-user-join-group", &cloudid.ClouduserGroupOptions{}) + cmd.Perform("cloud-user-leave-group", &cloudid.ClouduserGroupOptions{}) } diff --git a/cmd/climc/shell/cloudid/samlprovider.go b/cmd/climc/shell/cloudid/samlprovider.go new file mode 100644 index 0000000000..6561d5763b --- /dev/null +++ b/cmd/climc/shell/cloudid/samlprovider.go @@ -0,0 +1,29 @@ +// 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 cloudid + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options/cloudid" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.SAMLProviders).WithKeyword("saml-provider") + cmd.List(&cloudid.SAMLProviderListOptions{}) + cmd.Create(&cloudid.SAMLProviderCreateOptions{}) + cmd.Show(&cloudid.SAMLProviderIdOptions{}) + cmd.Delete(&cloudid.SAMLProviderIdOptions{}) +} diff --git a/cmd/climc/shell/cloudid/samluser.go b/cmd/climc/shell/cloudid/samluser.go new file mode 100644 index 0000000000..67525d7a46 --- /dev/null +++ b/cmd/climc/shell/cloudid/samluser.go @@ -0,0 +1,29 @@ +// 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 cloudid + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options/cloudid" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.Samlusers).WithKeyword("saml-user") + cmd.List(&cloudid.SamluserListOptions{}) + cmd.Create(&cloudid.SamluserCreateOptions{}) + cmd.Show(&cloudid.SamluserIdOptions{}) + cmd.Delete(&cloudid.SamluserIdOptions{}) +} diff --git a/pkg/apigateway/handler/proxy.go b/pkg/apigateway/handler/proxy.go index f0e2ed2876..ff48b6cc7f 100644 --- a/pkg/apigateway/handler/proxy.go +++ b/pkg/apigateway/handler/proxy.go @@ -42,8 +42,7 @@ func NewProxyHandlerWithService(prefix string, serviceName string) *InfluxdbProx } func requestManipulator(ctx context.Context, r *http.Request) (*http.Request, error) { - r.Header.Del("Cookie") - token := AppContextToken(ctx) + token, _, _ := fetchAuthInfo(ctx, r) if token != nil { r.Header.Set("X-Auth-Token", token.GetTokenString()) } diff --git a/pkg/apis/cloudid/cloudgroup.go b/pkg/apis/cloudid/cloudgroup.go index c753a52963..e15a7d7f1f 100644 --- a/pkg/apis/cloudid/cloudgroup.go +++ b/pkg/apis/cloudid/cloudgroup.go @@ -162,3 +162,13 @@ type CloudgroupSyncInput struct { type CloudgroupUpdateInput struct { } + +type CloudgroupResourceListInput struct { + // 根据权限组Id过滤资源 + CloudgroupId string `json:"cloudgroup_id"` +} + +type CloudgroupResourceDetails struct { + // 公有云用户名称 + Cloudgroup string `json:"cloudgroup"` +} diff --git a/pkg/apis/cloudid/cloudrole.go b/pkg/apis/cloudid/cloudrole.go new file mode 100644 index 0000000000..6620d78430 --- /dev/null +++ b/pkg/apis/cloudid/cloudrole.go @@ -0,0 +1,40 @@ +// 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 cloudid + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + CLOUD_ROLE_STATUS_CREATING = "creating" // 创建中 + CLOUD_ROLE_STATUS_AVAILABLE = "available" // 正常 + CLOUD_ROLE_STATUS_DELETING = "deleting" // 删除中 + CLOUD_ROLE_STATUS_DELETE_FAILED = "delete_failed" // 删除失败 +) + +type CloudroleListInput struct { + apis.StatusInfrasResourceBaseListInput + + CloudaccountResourceListInput +} + +type CloudroleDetails struct { + apis.StatusInfrasResourceBaseDetails + CloudaccountResourceDetails + + SCloudrole +} + +type CloudrolePurgeInput struct { +} diff --git a/pkg/apis/cloudid/saml_provider.go b/pkg/apis/cloudid/saml_provider.go new file mode 100644 index 0000000000..3ddd736be8 --- /dev/null +++ b/pkg/apis/cloudid/saml_provider.go @@ -0,0 +1,52 @@ +// 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 cloudid + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + SAML_PROVIDER_STATUS_AVAILABLE = "available" + SAML_PROVIDER_STATUS_NOT_MATCH = "not_match" + SAML_PROVIDER_STATUS_DELETING = "deleting" + SAML_PROVIDER_STATUS_DELETE_FAILED = "delete_failed" + SAML_PROVIDER_STATUS_UNKNOWN = "unknown" + SAML_PROVIDER_STATUS_CREATING = "creating" + SAML_PROVIDER_STATUS_CREATE_FAILED = "create_failed" + SAML_PROVIDER_STATUS_UNVALIABLE = "unavailable" +) + +type SAMLProviderListInput struct { + apis.StatusInfrasResourceBaseListInput + CloudaccountResourceListInput +} + +type SAMLProviderDetails struct { + apis.StatusInfrasResourceBaseDetails + CloudaccountResourceDetails + + SSAMLProvider +} + +type SAMLProviderCreateInput struct { + apis.StatusInfrasResourceBaseCreateInput + + CloudaccountId string `json:"cloudaccount_id"` + + // swagger:ignore + EntityId string `json:"entity_id"` + + // swagger:ignore + MetadataDocument string `json:"metadata_document"` +} diff --git a/pkg/apis/cloudid/samluser.go b/pkg/apis/cloudid/samluser.go new file mode 100644 index 0000000000..a8bf17ef6c --- /dev/null +++ b/pkg/apis/cloudid/samluser.go @@ -0,0 +1,45 @@ +// 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 cloudid + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + SAML_USER_STATUS_AVAILABLE = "available" +) + +type SamluserCreateInput struct { + apis.StatusDomainLevelUserResourceCreateInput + + // 权限组和账号必须是同一平台 + CloudgroupId string `json:"cloudgroup_id"` + + // 权限组和账号必须是同一平台 + CloudaccountId string `json:"cloudaccount_id"` +} + +type SamluserListInput struct { + apis.StatusDomainLevelUserResourceListInput + CloudgroupResourceListInput + CloudaccountResourceListInput +} + +type SamluserDetails struct { + apis.StatusDomainLevelUserResourceDetails + CloudgroupResourceDetails + CloudaccountResourceDetails + + SSamluser +} diff --git a/pkg/apis/cloudid/zz_generated.model.go b/pkg/apis/cloudid/zz_generated.model.go index e827aa0163..1682ecf6f9 100644 --- a/pkg/apis/cloudid/zz_generated.model.go +++ b/pkg/apis/cloudid/zz_generated.model.go @@ -18,13 +18,20 @@ import ( "yunion.io/x/onecloud/pkg/apis" ) +// SAMLProviderResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SAMLProviderResourceBase. +type SAMLProviderResourceBase struct { + SAMLProviderId string `json:"saml_provider_id"` +} + // SCloudaccount is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudaccount. type SCloudaccount struct { apis.SStandaloneResourceBase apis.SDomainizedResourceBase + AccountId string `json:"account_id"` Provider string `json:"provider"` Brand string `json:"brand"` IamLoginUrl string `json:"iam_login_url"` + SAMLAuth *bool `json:"saml_auth,omitempty"` } // SCloudaccountResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudaccountResourceBase. @@ -52,6 +59,11 @@ type SCloudgroupPolicy struct { SCloudpolicyResourceBase } +// SCloudgroupResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudgroupResourceBase. +type SCloudgroupResourceBase struct { + CloudgroupId string `json:"cloudgroup_id"` +} + // SCloudgroupUser is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudgroupUser. type SCloudgroupUser struct { SCloudgroupJointsBase @@ -121,6 +133,16 @@ type SCloudproviderResourceBase struct { CloudproviderId string `json:"cloudprovider_id"` } +// SCloudrole is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudrole. +type SCloudrole struct { + apis.SEnabledStatusInfrasResourceBase + apis.SExternalizedResourceBase + SCloudaccountResourceBase + SAMLProviderResourceBase + Document interface{} `json:"document"` + OwnerId string `json:"owner_id"` +} + // SClouduser is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SClouduser. type SClouduser struct { apis.SStatusDomainLevelUserResourceBase @@ -152,3 +174,20 @@ type SClouduserPolicy struct { type SClouduserResourceBase struct { ClouduserId string `json:"clouduser_id"` } + +// SSAMLProvider is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SSAMLProvider. +type SSAMLProvider struct { + apis.SStatusInfrasResourceBase + apis.SExternalizedResourceBase + SCloudaccountResourceBase + EntityId string `json:"entity_id"` + MetadataDocument string `json:"metadata_document"` + AuthUrl string `json:"auth_url"` +} + +// SSamluser is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SSamluser. +type SSamluser struct { + apis.SStatusDomainLevelUserResourceBase + SCloudgroupResourceBase + SCloudaccountResourceBase +} diff --git a/pkg/apis/compute/cloudaccount.go b/pkg/apis/compute/cloudaccount.go index f7cf828410..ff8e6b20d7 100644 --- a/pkg/apis/compute/cloudaccount.go +++ b/pkg/apis/compute/cloudaccount.go @@ -191,6 +191,10 @@ type CloudaccountCreateInput struct { cloudprovider.SCloudaccount cloudprovider.SCloudaccountCredential + + // 是否启用SAML认证 + // default: false + SAMLAuth *bool `json:"saml_auth"` } type CloudaccountShareModeInput struct { @@ -404,13 +408,13 @@ type EnrollmentAccountQuery struct { type GetCloudaccountSamlOutput struct { // cloudaccount SAML ServiceProvider entity ID - EntityId string `json:"entity_id"` + EntityId string `json:"entity_id,allowempty"` // redirect login URL for this cloudaccount - RedirectLoginUrl string `json:"redirect_login_url"` + RedirectLoginUrl string `json:"redirect_login_url,allowempty"` // redirect logout URL for this cloudaccount - RedirectLogoutUrl string `json:"redirect_logout_url"` + RedirectLogoutUrl string `json:"redirect_logout_url,allowempty"` // metadata URL for this cloudaccount - MetadataUrl string `json:"metadata_url"` + MetadataUrl string `json:"metadata_url,allowempty"` // initial SAML SSO login URL for this cloudaccount - InitLoginUrl string `json:"init_login_url"` + InitLoginUrl string `json:"init_login_url,allowempty"` } diff --git a/pkg/apis/compute/zz_generated.model.go b/pkg/apis/compute/zz_generated.model.go index 505c983482..b443f2db58 100644 --- a/pkg/apis/compute/zz_generated.model.go +++ b/pkg/apis/compute/zz_generated.model.go @@ -202,6 +202,7 @@ type SCloudaccount struct { ProxySettingId string `json:"proxy_setting_id"` // 公有云子账号登录地址 IamLoginUrl string `json:"iam_login_url"` + SAMLAuth *bool `json:"saml_auth,omitempty"` } // SCloudprovider is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SCloudprovider. @@ -257,6 +258,11 @@ type SCloudproviderQuota struct { QuotaType string `json:"quota_type"` } +// SCloudproviderResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SCloudproviderResourceBase. +type SCloudproviderResourceBase struct { + CloudproviderId string `json:"cloudprovider_id"` +} + // SCloudproviderregion is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SCloudproviderregion. type SCloudproviderregion struct { apis.SJointResourceBase @@ -271,6 +277,12 @@ type SCloudproviderregion struct { LastAutoSyncAt time.Time `json:"last_auto_sync_at"` } +// SCloudproviderschedtag is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SCloudproviderschedtag. +type SCloudproviderschedtag struct { + SSchedtagJointsBase + SCloudproviderResourceBase +} + // SCloudregion is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SCloudregion. type SCloudregion struct { apis.SEnabledStatusStandaloneResourceBase @@ -291,6 +303,12 @@ type SCloudregionResourceBase struct { CloudregionId string `json:"cloudregion_id"` } +// SCloudregionschedtag is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SCloudregionschedtag. +type SCloudregionschedtag struct { + SSchedtagJointsBase + SCloudregionResourceBase +} + // SDBInstance is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDBInstance. type SDBInstance struct { apis.SVirtualResourceBase @@ -499,6 +517,8 @@ type SDisk struct { // swap: 交换盘 // example: sys DiskType string `json:"disk_type"` + // cpu架构 + OsArch string `json:"os_arch"` // # is persistent Nonpersistent bool `json:"nonpersistent"` // 是否标记为SSD磁盘 @@ -900,6 +920,9 @@ type SGuest struct { // 虚拟化技术 // example: kvm Hypervisor string `json:"hypervisor"` + // 虚拟机CPU架构 + // example: x86 arm + OsArch string `json:"os_arch"` // 套餐名称 InstanceType string `json:"instance_type"` } @@ -1182,6 +1205,8 @@ type SHuaweiCachedLbbg struct { // SInstanceSnapshot is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SInstanceSnapshot. type SInstanceSnapshot struct { apis.SVirtualResourceBase + apis.SExternalizedResourceBase + SManagedResourceBase // 云主机Id GuestId string `json:"guest_id"` // 云主机配置 @@ -1198,6 +1223,8 @@ type SInstanceSnapshot struct { KeypairId string `json:"keypair_id"` // 操作系统类型 OsType string `json:"os_type"` + // CPU架构 + OsArch string `json:"os_arch"` // 套餐名称 InstanceType string `json:"instance_type"` } @@ -1253,6 +1280,7 @@ type SLoadbalancer struct { apis.SExternalizedResourceBase SManagedResourceBase SCloudregionResourceBase + SDeletePreventableResourceBase // LB might optionally be in a VPC, vpc_id, manager_id, cloudregion_id SVpcResourceBase // zone_id @@ -1814,6 +1842,33 @@ type SRouteTable struct { Routes *SRoutes `json:"routes"` } +// SRouteTableAssociation is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SRouteTableAssociation. +type SRouteTableAssociation struct { + apis.SStatusStandaloneResourceBase + apis.SExternalizedResourceBase + SRouteTableResourceBase + AssociationType string `json:"association_type"` + AssociatedResourceId string `json:"associated_resource_id"` + ExtAssociatedResourceId string `json:"ext_associated_resource_id"` +} + +// SRouteTableResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SRouteTableResourceBase. +type SRouteTableResourceBase struct { + RouteTableId string `json:"route_table_id"` +} + +// SRouteTableRouteSet is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SRouteTableRouteSet. +type SRouteTableRouteSet struct { + apis.SStatusStandaloneResourceBase + apis.SExternalizedResourceBase + SRouteTableResourceBase + Type string `json:"type"` + Cidr string `json:"cidr"` + NextHopType string `json:"next_hop_type"` + NextHopId string `json:"next_hop_id"` + ExtNextHopId string `json:"ext_next_hop_id"` +} + // SScalingActivity is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SScalingActivity. type SScalingActivity struct { apis.SStatusStandaloneResourceBase @@ -2085,6 +2140,7 @@ type SSnapshot struct { DiskType string `json:"disk_type"` // 操作系统类型 OsType string `json:"os_type"` + OsArch string `json:"os_arch"` // create disk from snapshot, snapshot as disk backing file RefCount int `json:"ref_count"` BackingDiskId string `json:"backing_disk_id"` @@ -2253,9 +2309,11 @@ type SVpcPeeringConnection struct { apis.SEnabledStatusInfrasResourceBase apis.SExternalizedResourceBase SVpcResourceBase - PeerVpcId string `json:"peer_vpc_id"` - PeerAccountId string `json:"peer_account_id"` - Bandwidth int `json:"bandwidth"` + ExtPeerVpcId string `json:"ext_peer_vpc_id"` + ExtPeerAccountId string `json:"ext_peer_account_id"` + PeerVpcId string `json:"peer_vpc_id"` + PeerAccountId string `json:"peer_account_id"` + Bandwidth int `json:"bandwidth"` } // SVpcResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SVpcResourceBase. @@ -2300,3 +2358,9 @@ type SZone struct { type SZoneResourceBase struct { ZoneId string `json:"zone_id"` } + +// SZoneschedtag is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SZoneschedtag. +type SZoneschedtag struct { + SSchedtagJointsBase + SZoneResourceBase +} diff --git a/pkg/apis/image/zz_generated.model.go b/pkg/apis/image/zz_generated.model.go index 6c8b5ab717..c3a4596782 100644 --- a/pkg/apis/image/zz_generated.model.go +++ b/pkg/apis/image/zz_generated.model.go @@ -22,6 +22,8 @@ import ( type SGuestImage struct { apis.SSharableVirtualResourceBase Protected *bool `json:"protected,omitempty"` + // 操作系统CPU架构 + OsArch string `json:"os_arch"` } // SGuestImageJoint is an autogenerated struct via yunion.io/x/onecloud/pkg/image/models.SGuestImageJoint. @@ -57,6 +59,8 @@ type SImage struct { IsGuestImage *bool `json:"is_guest_image,omitempty"` // 是否是数据盘镜像 IsData *bool `json:"is_data,omitempty"` + // 操作系统CPU架构 + OsArch string `json:"os_arch"` // image copy from url, save origin checksum before probe // 从镜像时长导入的镜像校验和 OssChecksum string `json:"oss_checksum"` diff --git a/pkg/apis/monitor/zz_generated.model.go b/pkg/apis/monitor/zz_generated.model.go index d428ddadf0..718508a622 100644 --- a/pkg/apis/monitor/zz_generated.model.go +++ b/pkg/apis/monitor/zz_generated.model.go @@ -53,9 +53,14 @@ type SAlertDashBoard struct { apis.SEnabledResourceBase apis.SStatusStandaloneResourceBase apis.SScopedResourceBase - Refresh string `json:"refresh"` - Settings interface{} `json:"settings"` - Message string `json:"message"` + Refresh string `json:"refresh"` +} + +// SAlertDashboardPanel is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SAlertDashboardPanel. +type SAlertDashboardPanel struct { + apis.SVirtualJointResourceBase + DashboardId string `json:"dashboard_id"` + PanelId string `json:"panel_id"` } // SAlertJointsBase is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SAlertJointsBase. @@ -64,6 +69,14 @@ type SAlertJointsBase struct { AlertId string `json:"alert_id"` } +// SAlertPanel is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SAlertPanel. +type SAlertPanel struct { + apis.SStatusStandaloneResourceBase + apis.SScopedResourceBase + Settings interface{} `json:"settings"` + Message string `json:"message"` +} + // SAlertRecord is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SAlertRecord. type SAlertRecord struct { // db.SVirtualResourceBase diff --git a/pkg/cloudid/models/cloudaccount.go b/pkg/cloudid/models/cloudaccount.go index 480771b3a8..4b11905e32 100644 --- a/pkg/cloudid/models/cloudaccount.go +++ b/pkg/cloudid/models/cloudaccount.go @@ -17,8 +17,10 @@ package models import ( "context" "database/sql" + "fmt" "net/http" "net/url" + "strings" "golang.org/x/net/http/httpproxy" @@ -67,9 +69,11 @@ type SCloudaccount struct { db.SStandaloneResourceBase db.SDomainizedResourceBase - Provider string `width:"64" charset:"ascii" list:"domain"` - Brand string `width:"64" charset:"utf8" nullable:"true" list:"domain"` - IamLoginUrl string `width:"512" charset:"ascii"` + AccountId string `width:"128" charset:"utf8" nullable:"true" list:"domain" create:"domain_optional"` + Provider string `width:"64" charset:"ascii" list:"domain"` + Brand string `width:"64" charset:"utf8" nullable:"true" list:"domain"` + IamLoginUrl string `width:"512" charset:"ascii"` + SAMLAuth tristate.TriState `nullable:"false" list:"domain" default:"false"` } func (manager *SCloudaccountManager) GetResourceCount() ([]db.SScopeResourceCount, error) { @@ -206,6 +210,34 @@ func (self *SCloudaccount) removeCloudgroupcaches(ctx context.Context, userCred return nil } +func (self *SCloudaccount) removeSAMLProviders(ctx context.Context, userCred mcclient.TokenCredential) error { + samls, err := self.GetSAMLProviders() + if err != nil { + return errors.Wrap(err, "GetSAMLProviders") + } + for i := range samls { + err = samls[i].RealDelete(ctx, userCred) + if err != nil { + return errors.Wrap(err, "samls[i].RealDelete") + } + } + return nil +} + +func (self *SCloudaccount) removeCloudroles(ctx context.Context, userCred mcclient.TokenCredential) error { + roles, err := self.GetCloudroles() + if err != nil { + return errors.Wrapf(err, "GetCloudroles") + } + for i := range roles { + err = roles[i].RealDelete(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "roles.RealDelete") + } + } + return nil +} + func (self *SCloudaccount) syncRemoveCloudaccount(ctx context.Context, userCred mcclient.TokenCredential) error { err := self.syncRemoveClouduser(ctx, userCred) if err != nil { @@ -222,9 +254,48 @@ func (self *SCloudaccount) syncRemoveCloudaccount(ctx context.Context, userCred return errors.Wrap(err, "removeCloudgroupcaches") } + err = self.removeSAMLProviders(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "removeSAMLProviders") + } + + err = self.removeCloudroles(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "removeCloudroles") + } + + err = self.removeSamluser(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "syncRemoveSamluser") + } + return self.Delete(ctx, userCred) } +func (self *SCloudaccount) GetSamlusers() ([]SSamluser, error) { + q := SamluserManager.Query().Equals("cloudaccount_id", self.Id) + users := []SSamluser{} + err := db.FetchModelObjects(SamluserManager, q, &users) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return users, nil +} + +func (self *SCloudaccount) removeSamluser(ctx context.Context, userCred mcclient.TokenCredential) error { + users, err := self.GetSamlusers() + if err != nil { + return errors.Wrapf(err, "GetSamusers") + } + for i := range users { + err = users[i].Delete(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "delete %s(%s)", users[i].Name, users[i].Id) + } + } + return nil +} + func (self *SCloudaccount) syncRemoveClouduser(ctx context.Context, userCred mcclient.TokenCredential) error { users, err := self.getCloudusers() if err != nil { @@ -248,6 +319,9 @@ func (manager *SCloudaccountManager) newFromICloudaccount(ctx context.Context, u if err != nil { return nil, errors.Wrap(err, "Insert") } + if account.SAMLAuth.IsTrue() { + account.StartSAMLProviderCreateTask(ctx, userCred) + } return account, nil } @@ -258,11 +332,14 @@ func (self *SCloudaccount) syncWithICloudaccount(ctx context.Context, userCred m self.DomainId = account.DomainId self.Brand = account.Brand self.IamLoginUrl = account.IamLoginUrl + self.SAMLAuth = account.SAMLAuth + self.AccountId = account.AccountId return nil }) if err != nil { return errors.Wrap(err, "db.UpdateWithLock") } + self.StartSAMLProviderCreateTask(ctx, userCred) return nil } @@ -278,6 +355,16 @@ func (manager *SCloudaccountManager) SyncCloudaccounts(ctx context.Context, user } } +func (self *SCloudaccount) StartSyncSamlProvidersTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + params := jsonutils.NewDict() + task, err := taskman.TaskManager.NewTask(ctx, "SyncSAMLProvidersTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + task.ScheduleRun(nil) + return nil +} + func (self SCloudaccount) GetGlobalId() string { return self.Id } @@ -407,6 +494,8 @@ func (account *SCloudDelegate) GetProvider() (cloudprovider.ICloudProvider, erro Account: account.Account, Secret: passwd, ProxyFunc: proxyFunc, + + AccountId: account.Id, }) } @@ -463,14 +552,12 @@ func (self *SCloudaccount) SyncCloudusers(ctx context.Context, userCred mcclient } for i := 0; i < len(removed); i++ { - if len(removed[i].ExternalId) > 0 { - err = removed[i].RealDelete(ctx, userCred) - if err != nil { - result.DeleteError(err) - continue - } - result.Delete() + err = removed[i].RealDelete(ctx, userCred) + if err != nil { + result.DeleteError(err) + continue } + result.Delete() } for i := 0; i < len(commondb); i++ { @@ -860,7 +947,7 @@ func (self *SCloudaccount) syncCloudprovider(ctx context.Context, userCred mccli for i := 0; i < len(removed); i++ { err = removed[i].Delete(ctx, userCred) if err != nil { - result.AddError(err) + result.DeleteError(err) continue } result.Delete() @@ -970,6 +1057,180 @@ func (manager *SCloudaccountManager) SyncCloudidResources(ctx context.Context, u } } +func (self *SCloudaccount) IsSAMLProviderValid() (*SSAMLProvider, bool) { + provider, err := self.RegisterSAMProvider() + if err != nil { + return provider, false + } + if len(provider.ExternalId) == 0 { + return provider, false + } + return provider, true +} + +func (self *SCloudaccount) RegisterSAMProvider() (*SSAMLProvider, error) { + if len(options.Options.ApiServer) == 0 { + return nil, fmt.Errorf("empty api server") + } + sps, err := self.GetSAMLProviders() + if err != nil { + return nil, errors.Wrapf(err, "GetSAMLProviders") + } + for i := range sps { + if sps[i].EntityId == options.Options.ApiServer { + return &sps[i], nil + } + } + sp := &SSAMLProvider{} + sp.SetModelManager(SAMLProviderManager, sp) + sp.Name = func() string { + name := strings.TrimPrefix(options.Options.ApiServer, "https://") + name = strings.TrimPrefix(name, "http://") + return name + }() + sp.EntityId = options.Options.ApiServer + sp.CloudaccountId = self.Id + sp.Status = api.SAML_PROVIDER_STATUS_CREATING + metadata := SamlIdpInstance().GetMetadata(self.Id).String() + sp.MetadataDocument = metadata + err = SAMLProviderManager.TableSpec().Insert(context.TODO(), sp) + if err != nil { + return nil, errors.Wrapf(err, "Insert") + } + return sp, nil +} + +func (self *SCloudaccount) StartSAMLProviderCreateTask(ctx context.Context, userCred mcclient.TokenCredential) error { + if self.SAMLAuth.IsFalse() { + return nil + } + sp, valid := self.IsSAMLProviderValid() + if valid { + return nil + } + return sp.StartSAMLProviderCreateTask(ctx, userCred, "") +} + +func (manager *SCloudaccountManager) SyncSAMLProviders(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + accounts, err := manager.GetSupportCloudIdAccounts() + if err != nil { + log.Errorf("GetSupportCloudIdAccounts error: %v", err) + return + } + for i := range accounts { + err = accounts[i].StartSyncSamlProvidersTask(ctx, userCred, "") + if err != nil { + log.Errorf("StartSyncSamlProvidersTask for account %s(%s) error: %v", accounts[i].Name, accounts[i].Provider, err) + } + } +} + +func (manager *SCloudaccountManager) SyncCloudroles(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + accounts, err := manager.GetSupportCloudIdAccounts() + if err != nil { + log.Errorf("GetSupportCloudIdAccounts error: %v", err) + return + } + for i := range accounts { + err = accounts[i].StartSyncCloudrolesTask(ctx, userCred, "") + if err != nil { + log.Errorf("StartSyncCloudrolesTask for account %s(%s) error: %v", accounts[i].Name, accounts[i].Provider, err) + } + } +} + +func (self *SCloudaccount) StartSyncCloudrolesTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + params := jsonutils.NewDict() + task, err := taskman.TaskManager.NewTask(ctx, "SyncCloudrolesTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + task.ScheduleRun(nil) + return nil +} + +func (self *SCloudaccount) GetSAMLProviders() ([]SSAMLProvider, error) { + q := SAMLProviderManager.Query().Equals("cloudaccount_id", self.Id) + samls := []SSAMLProvider{} + err := db.FetchModelObjects(SAMLProviderManager, q, &samls) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return samls, nil +} + +func (self *SCloudaccount) SyncSAMLProviders(ctx context.Context, userCred mcclient.TokenCredential, samls []cloudprovider.ICloudSAMLProvider) compare.SyncResult { + + result := compare.SyncResult{} + + dbSamls, err := self.GetSAMLProviders() + if err != nil { + result.Error(errors.Wrap(err, "GetSAMLProviders")) + return result + } + + removed := make([]SSAMLProvider, 0) + commondb := make([]SSAMLProvider, 0) + commonext := make([]cloudprovider.ICloudSAMLProvider, 0) + added := make([]cloudprovider.ICloudSAMLProvider, 0) + + err = compare.CompareSets(dbSamls, samls, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrap(err, "compare.CompareSets")) + return result + } + + for i := 0; i < len(removed); i++ { + err = removed[i].RealDelete(ctx, userCred) + if err != nil { + result.DeleteError(err) + continue + } + result.Delete() + } + + for i := 0; i < len(commondb); i++ { + err = commondb[i].SyncWithCloudSAMLProvider(ctx, userCred, commonext[i]) + if err != nil { + result.UpdateError(err) + continue + } + result.Update() + } + + for i := 0; i < len(added); i++ { + err = self.newFromCloudSAMLProvider(ctx, userCred, added[i]) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + return result +} + +func (self *SCloudaccount) newFromCloudSAMLProvider(ctx context.Context, userCred mcclient.TokenCredential, ext cloudprovider.ICloudSAMLProvider) error { + saml := &SSAMLProvider{} + saml.SetModelManager(SAMLProviderManager, saml) + saml.Name = ext.GetName() + saml.ExternalId = ext.GetGlobalId() + saml.DomainId = self.DomainId + saml.CloudaccountId = self.Id + metadata, err := ext.GetMetadataDocument() + if err != nil { + log.Errorf("failed to get metadata from %s(%s) error: %v", self.Name, self.Provider, err) + } + saml.Status = ext.GetStatus() + if metadata != nil { + saml.EntityId = metadata.EntityId + saml.MetadataDocument = metadata.String() + } + if saml.EntityId != options.Options.ApiServer { + saml.Status = api.SAML_PROVIDER_STATUS_NOT_MATCH + } + return SAMLProviderManager.TableSpec().Insert(ctx, saml) +} + func (self *SCloudaccount) StartSyncCloudIdResourcesTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { params := jsonutils.NewDict() task, err := taskman.TaskManager.NewTask(ctx, "SyncCloudIdResourcesTask", self, userCred, params, parentTaskId, "", nil) @@ -999,14 +1260,12 @@ func (self *SCloudaccount) SyncCloudgroupcaches(ctx context.Context, userCred mc } for i := 0; i < len(removed); i++ { - if len(removed[i].ExternalId) > 0 { // 只删除云上已经删除过的组 - err = removed[i].RealDelete(ctx, userCred) - if err != nil { - result.DeleteError(err) - continue - } - result.Delete() + err = removed[i].RealDelete(ctx, userCred) + if err != nil { + result.DeleteError(err) + continue } + result.Delete() } for i := 0; i < len(commondb); i++ { @@ -1335,3 +1594,174 @@ func (self *SCloudaccount) SyncSystemCloudpoliciesForCloud(ctx context.Context, log.Infof("Sync %s(%s) system policies for user %s result: %s", self.Name, self.Provider, clouduser.Name, result.Result()) return nil } + +func (self *SCloudaccount) GetLocalUserCloudroles(userId, spId string) ([]SCloudrole, error) { + roles := []SCloudrole{} + q := CloudroleManager.Query().Equals("cloudaccount_id", self.Id).Equals("owner_id", userId).Equals("saml_provider_id", spId) + err := db.FetchModelObjects(CloudroleManager, q, &roles) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return roles, nil +} + +func (self *SCloudaccount) RegisterCloudrole(userId, spId string) (*SCloudrole, error) { + roles, err := self.GetLocalUserCloudroles(userId, spId) + if err != nil { + return nil, errors.Wrapf(err, "GetLocalUserCloudroles") + } + if len(roles) > 0 { + return &roles[0], nil + } + user, err := db.UserCacheManager.FetchById(userId) + if err != nil { + return nil, errors.Wrapf(err, "UserCacheManager.FetchById(%s)", userId) + } + role := &SCloudrole{} + role.SetModelManager(CloudroleManager, role) + role.CloudaccountId = self.Id + role.OwnerId = userId + role.SAMLProviderId = spId + role.Name = user.GetName() + role.Status = api.CLOUD_ROLE_STATUS_CREATING + role.DomainId = self.DomainId + return role, CloudroleManager.TableSpec().Insert(context.TODO(), role) +} + +func (self *SCloudaccount) GetCloudrole(userId string) (*SCloudrole, error) { + sp, valid := self.IsSAMLProviderValid() + if !valid { + return nil, fmt.Errorf("SAMLProvider for account %s not ready", self.Id) + } + + return self.RegisterCloudrole(userId, sp.Id) +} + +func (self *SCloudaccount) SyncRole(userId string) (*SCloudrole, error) { + role, err := self.GetCloudrole(userId) + if err != nil { + return nil, errors.Wrapf(err, "GetCloudrole") + } + + err = role.SyncRoles() + if err != nil { + return nil, errors.Wrapf(err, "SyncRoles") + } + + return role, nil +} + +func (self *SCloudaccount) GetCloudroles() ([]SCloudrole, error) { + roles := []SCloudrole{} + q := CloudroleManager.Query() + err := db.FetchModelObjects(CloudroleManager, q, &roles) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return roles, nil +} + +func (self *SCloudaccount) newCloudrole(ctx context.Context, userCred mcclient.TokenCredential, iRole cloudprovider.ICloudrole) error { + role := &SCloudrole{} + role.SetModelManager(CloudroleManager, role) + role.Name = iRole.GetName() + role.ExternalId = iRole.GetGlobalId() + role.Document = iRole.GetDocument() + if spId := iRole.GetSAMLProvider(); len(spId) > 0 { + sp, _ := db.FetchByExternalIdAndManagerId(SAMLProviderManager, spId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + return q.Equals("cloudaccount_id", self.Id) + }) + if sp != nil { + role.SAMLProviderId = sp.GetId() + } + } + role.CloudaccountId = self.Id + role.Status = api.CLOUD_ROLE_STATUS_AVAILABLE + return CloudroleManager.TableSpec().Insert(ctx, role) +} + +func (self *SCloudaccount) SyncCloudroles(ctx context.Context, userCred mcclient.TokenCredential, exts []cloudprovider.ICloudrole) compare.SyncResult { + result := compare.SyncResult{} + + roles, err := self.GetCloudroles() + if err != nil { + result.Error(errors.Wrapf(err, "GetCloudroles")) + return result + } + + removed := make([]SCloudrole, 0) + commondb := make([]SCloudrole, 0) + commonext := make([]cloudprovider.ICloudrole, 0) + added := make([]cloudprovider.ICloudrole, 0) + + err = compare.CompareSets(roles, exts, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrapf(err, "compare.CompareSets")) + return result + } + + for i := 0; i < len(removed); i++ { + err = removed[i].RealDelete(ctx, userCred) + if err != nil { + result.DeleteError(err) + continue + } + result.Delete() + } + + for i := 0; i < len(commondb); i++ { + err = commondb[i].syncWithCloudrole(ctx, userCred, commonext[i]) + if err != nil { + result.UpdateError(err) + continue + } + result.Update() + } + + for i := 0; i < len(added); i++ { + err := self.newCloudrole(ctx, userCred, added[i]) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + + return result +} + +func (self *SCloudaccount) GetUserCloudgroups(userId string) ([]string, error) { + ret := []string{} + q := CloudgroupManager.Query() + samlusers := SamluserManager.Query("cloudgroup_id").Equals("owner_id", userId).Equals("cloudaccount_id", self.Id).SubQuery() + q = q.In("id", samlusers) + groups := []SCloudgroup{} + err := db.FetchModelObjects(CloudgroupManager, q, &groups) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + if len(groups) == 0 { + return ret, nil + } + for i := range groups { + cache, err := CloudgroupcacheManager.Register(&groups[i], self) + if err != nil { + return []string{}, errors.Wrapf(err, "group cache Register") + } + if len(cache.ExternalId) > 0 { + ret = append(ret, cache.Name) + } else { + s := auth.GetAdminSession(context.TODO(), options.Options.Region, "") + _, err = cache.GetOrCreateICloudgroup(context.TODO(), s.GetToken()) + if err != nil { + return []string{}, errors.Wrapf(err, "GetOrCreateICloudgroup") + } + cache, err := CloudgroupcacheManager.Register(&groups[i], self) + if err != nil { + return []string{}, errors.Wrapf(err, "group cache Register") + } + ret = append(ret, cache.Name) + } + } + return ret, nil +} diff --git a/pkg/cloudid/models/cloudgroup.go b/pkg/cloudid/models/cloudgroup.go index 0b01f7899d..4dbc830b25 100644 --- a/pkg/cloudid/models/cloudgroup.go +++ b/pkg/cloudid/models/cloudgroup.go @@ -255,9 +255,37 @@ func (self *SCloudgroup) RealDelete(ctx context.Context, userCred mcclient.Token if err != nil { return errors.Wrap(err, "remoteUsers") } + err = self.removeSamlusers() + if err != nil { + return errors.Wrapf(err, "removeSamlusers") + } return self.SStatusInfrasResourceBase.Delete(ctx, userCred) } +func (self *SCloudgroup) GetSamlusers() ([]SSamluser, error) { + q := SamluserManager.Query().Equals("cloudgroup_id", self.Id) + users := []SSamluser{} + err := db.FetchModelObjects(SamluserManager, q, &users) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return users, nil +} + +func (self *SCloudgroup) removeSamlusers() error { + users, err := self.GetSamlusers() + if err != nil { + return errors.Wrap(err, "GetSamlusers") + } + for i := range users { + err = users[i].Delete(context.TODO(), nil) + if err != nil { + return errors.Wrapf(err, "rm saml user(%s)", users[i].Id) + } + } + return nil +} + func (self *SCloudgroup) removeUsers() error { users, err := self.GetCloudusers() if err != nil { diff --git a/pkg/cloudid/models/cloudgroupcaches.go b/pkg/cloudid/models/cloudgroupcaches.go index 5a77752af7..08242e7322 100644 --- a/pkg/cloudid/models/cloudgroupcaches.go +++ b/pkg/cloudid/models/cloudgroupcaches.go @@ -283,6 +283,7 @@ func (self *SCloudgroupcache) GetOrCreateICloudgroup(ctx context.Context, userCr return nil, errors.Wrap(err, "CreateICloudgroup") } _, err = db.Update(self, func() error { + self.Name = groupName self.ExternalId = iGroup.GetGlobalId() self.Status = api.CLOUD_GROUP_CACHE_STATUS_AVAILABLE return nil diff --git a/pkg/cloudid/models/cloudgroupresource.go b/pkg/cloudid/models/cloudgroupresource.go new file mode 100644 index 0000000000..a7447e8a0d --- /dev/null +++ b/pkg/cloudid/models/cloudgroupresource.go @@ -0,0 +1,90 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/reflectutils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SCloudgroupResourceBaseManager struct { +} + +type SCloudgroupResourceBase struct { + CloudgroupId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` +} + +func (self *SCloudgroupJointsBase) GetCloudgroup() (*SCloudgroup, error) { + group, err := CloudgroupManager.FetchById(self.CloudgroupId) + if err != nil { + return nil, errors.Wrap(err, "FetchById") + } + return group.(*SCloudgroup), nil +} + +func (manager *SCloudgroupResourceBaseManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, groupCred mcclient.TokenCredential, query api.CloudgroupResourceListInput) (*sqlchemy.SQuery, error) { + if len(query.CloudgroupId) > 0 { + group, err := CloudgroupManager.FetchByIdOrName(nil, query.CloudgroupId) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudgroup", query.CloudgroupId) + } + return nil, httperrors.NewGeneralError(err) + } + q = q.Equals("cloudgroup_id", group.GetId()) + } + return q, nil +} + +func (manager *SCloudgroupResourceBaseManager) FetchCustomizeColumns( + ctx context.Context, + groupCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.CloudgroupResourceDetails { + rows := make([]api.CloudgroupResourceDetails, len(objs)) + groupIds := make([]string, len(objs)) + for i := range objs { + var base *SCloudgroupResourceBase + err := reflectutils.FindAnonymouStructPointer(objs[i], &base) + if err != nil { + log.Errorf("Cannot find SCloudgroupResourceBase in %#v: %s", objs[i], err) + } else if base != nil && len(base.CloudgroupId) > 0 { + groupIds[i] = base.CloudgroupId + } + } + groupMaps, err := db.FetchIdNameMap2(CloudgroupManager, groupIds) + if err != nil { + return rows + } + for i := range rows { + rows[i].Cloudgroup, _ = groupMaps[groupIds[i]] + } + return rows +} diff --git a/pkg/cloudid/models/cloudrole.go b/pkg/cloudid/models/cloudrole.go new file mode 100644 index 0000000000..a043f8c911 --- /dev/null +++ b/pkg/cloudid/models/cloudrole.go @@ -0,0 +1,261 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "fmt" + "strconv" + "strings" + + "gopkg.in/fatih/set.v0" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SCloudroleManager struct { + db.SStatusInfrasResourceBaseManager + db.SExternalizedResourceBaseManager + SCloudaccountResourceBaseManager + SAMLProviderResourceBaseManager +} + +var CloudroleManager *SCloudroleManager + +func init() { + CloudroleManager = &SCloudroleManager{ + SStatusInfrasResourceBaseManager: db.NewStatusInfrasResourceBaseManager( + SCloudrole{}, + "cloudroles_tbl", + "cloudrole", + "cloudroles", + ), + } + CloudroleManager.SetVirtualObject(CloudroleManager) +} + +type SCloudrole struct { + db.SEnabledStatusInfrasResourceBase + db.SExternalizedResourceBase + SCloudaccountResourceBase + SAMLProviderResourceBase + + Document *jsonutils.JSONDict `length:"long" charset:"ascii" list:"domain" update:"domain" create:"domain_required"` + OwnerId string `width:"128" charset:"ascii" index:"true" list:"user" nullable:"false" create:"optional"` +} + +// 公有云角色列表 +func (manager *SCloudroleManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.CloudroleListInput) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusInfrasResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusInfrasResourceBaseListInput) + if err != nil { + return nil, err + } + + q, err = manager.SCloudaccountResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudaccountResourceListInput) + if err != nil { + return nil, err + } + + return q, nil +} + +// 获取公有云角色详情 +func (self *SCloudrole) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.CloudroleDetails, error) { + return api.CloudroleDetails{}, nil +} + +func (manager *SCloudroleManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.CloudroleDetails { + rows := make([]api.CloudroleDetails, len(objs)) + infRows := manager.SStatusInfrasResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + acRows := manager.SCloudaccountResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.CloudroleDetails{ + StatusInfrasResourceBaseDetails: infRows[i], + CloudaccountResourceDetails: acRows[i], + } + } + return rows +} + +// 删除公有云角色 +func (self *SCloudrole) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.StartCloudroleDeleteTask(ctx, userCred, false, "") +} + +func (self *SCloudrole) StartCloudroleDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, purge bool, parentTaskId string) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewBool(purge), "purge") + task, err := taskman.TaskManager.NewTask(ctx, "CloudroleDeleteTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.CLOUD_ROLE_STATUS_DELETING, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SCloudrole) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "purge") +} + +// 清除角色(不删除云上资源) +func (self *SCloudrole) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudrolePurgeInput) (jsonutils.JSONObject, error) { + return nil, self.StartCloudroleDeleteTask(ctx, userCred, true, "") +} + +func (self *SCloudrole) GetICloudrole() (cloudprovider.ICloudrole, error) { + account, err := self.GetCloudaccount() + if err != nil { + return nil, errors.Wrapf(err, "GetCloudaccount") + } + provider, err := account.GetProvider() + if err != nil { + return nil, errors.Wrapf(err, "GetProvider") + } + if len(self.ExternalId) > 0 { + iRole, err := provider.GetICloudroleById(self.ExternalId) + if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound { + return nil, errors.Wrapf(err, "GetICloudroleById(%s)", self.ExternalId) + } + if err == nil { + return iRole, nil + } + } + sp, err := self.GetSAMLProvider() + if err != nil { + return nil, errors.Wrapf(err, "GetSAMLProvider") + } + for i := 0; i < 10; i++ { + _, err := provider.GetICloudroleByName(self.Name) + if err != nil && errors.Cause(err) == cloudprovider.ErrNotFound { + break + } + info := strings.Split(self.Name, "-") + num, err := strconv.Atoi(info[len(info)-1]) + if err != nil { + info = append(info, "1") + } else { + info[len(info)-1] = fmt.Sprintf("%d", num+1) + } + self.Name = strings.Join(info, "-") + } + opts := &cloudprovider.SRoleCreateOptions{ + Name: self.Name, + Desc: self.Description, + SAMLProvider: sp.ExternalId, + } + iRole, err := provider.CreateICloudrole(opts) + if err != nil { + return nil, errors.Wrapf(err, "CreateICloudrole") + } + db.Update(self, func() error { + self.ExternalId = iRole.GetGlobalId() + self.Name = iRole.GetName() + self.Document = iRole.GetDocument() + self.Status = api.CLOUD_ROLE_STATUS_AVAILABLE + return nil + }) + return iRole, nil +} + +func (self *SCloudrole) GetCloudpolicies() ([]SCloudpolicy, error) { + q := CloudpolicyManager.Query() + samlUsers := SamluserManager.Query("cloudgroup_id").Equals("owner_id", self.OwnerId).Equals("cloudaccount_id", self.CloudaccountId).SubQuery() + groups := CloudgroupManager.Query("id").In("id", samlUsers) + gp := CloudgroupPolicyManager.Query("cloudpolicy_id").In("cloudgroup_id", groups).SubQuery() + q = q.In("id", gp) + policies := []SCloudpolicy{} + err := db.FetchModelObjects(CloudpolicyManager, q, &policies) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return policies, nil +} + +func (self *SCloudrole) SyncRoles() error { + iRole, err := self.GetICloudrole() + if err != nil { + return errors.Wrapf(err, "GetICloudrole") + } + policies, err := self.GetCloudpolicies() + if err != nil { + return errors.Wrapf(err, "GetICloudpolicies") + } + local := set.New(set.ThreadSafe) + for i := range policies { + if policies[i].PolicyType == api.CLOUD_POLICY_TYPE_SYSTEM { + local.Add(policies[i].ExternalId) + } else { + } + } + iPolicies, err := iRole.GetICloudpolicies() + if err != nil { + return errors.Wrapf(err, "GetICloudpolicies") + } + remote := set.New(set.ThreadSafe) + for i := range iPolicies { + remote.Add(iPolicies[i].GetGlobalId()) + } + for _, id := range set.Difference(remote, local).List() { + err = iRole.DetachPolicy(id.(string)) + if err != nil { + return errors.Wrapf(err, "DetachPolicy(%s)", id) + } + } + for _, id := range set.Difference(local, remote).List() { + err = iRole.AttachPolicy(id.(string)) + if err != nil { + return errors.Wrapf(err, "AttachPolicy(%s)", id) + } + } + + return nil +} + +func (self *SCloudrole) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SStatusInfrasResourceBase.Delete(ctx, userCred) +} + +func (self *SCloudrole) syncWithCloudrole(ctx context.Context, userCred mcclient.TokenCredential, iRole cloudprovider.ICloudrole) error { + _, err := db.Update(self, func() error { + self.Name = self.GetName() + self.Document = iRole.GetDocument() + self.Status = api.CLOUD_ROLE_STATUS_AVAILABLE + return nil + }) + return err +} diff --git a/pkg/cloudid/models/saml_provider.go b/pkg/cloudid/models/saml_provider.go new file mode 100644 index 0000000000..adff8df61d --- /dev/null +++ b/pkg/cloudid/models/saml_provider.go @@ -0,0 +1,212 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/validators" + "yunion.io/x/onecloud/pkg/cloudid/options" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/samlutils" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SSAMLProviderManager struct { + db.SStatusInfrasResourceBaseManager + db.SExternalizedResourceBaseManager + SCloudaccountResourceBaseManager +} + +var SAMLProviderManager *SSAMLProviderManager + +func init() { + SAMLProviderManager = &SSAMLProviderManager{ + SStatusInfrasResourceBaseManager: db.NewStatusInfrasResourceBaseManager( + SSAMLProvider{}, + "saml_provider_tbl", + "saml_provider", + "saml_providers", + ), + } + SAMLProviderManager.SetVirtualObject(SAMLProviderManager) +} + +type SSAMLProvider struct { + db.SStatusInfrasResourceBase + db.SExternalizedResourceBase + SCloudaccountResourceBase + + EntityId string `get:"domain" create:"domain_optional" list:"domain"` + MetadataDocument string `get:"domain" create:"domain_optional"` + AuthUrl string `width:"512" charset:"ascii" get:"domain" list:"domain"` +} + +func (manager *SSAMLProviderManager) GetIVirtualModelManager() db.IVirtualModelManager { + return manager.GetVirtualObject().(db.IVirtualModelManager) +} + +func (manager *SSAMLProviderManager) GetResourceCount() ([]db.SScopeResourceCount, error) { + return nil, nil +} + +func (manager *SSAMLProviderManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject { + accountId, _ := data.GetString("cloudaccount_id") + return jsonutils.Marshal(map[string]string{"cloudaccount_id": accountId}) +} + +func (manager *SSAMLProviderManager) FilterByUniqValues(q *sqlchemy.SQuery, values jsonutils.JSONObject) *sqlchemy.SQuery { + accountId, _ := values.GetString("cloudaccount_id") + if len(accountId) > 0 { + q = q.Equals("cloudaccount_id", accountId) + } + return q +} + +// 创建云账号的身份提供商 +func (manager *SSAMLProviderManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.SAMLProviderCreateInput) (api.SAMLProviderCreateInput, error) { + if len(input.CloudaccountId) == 0 { + return input, httperrors.NewMissingParameterError("cloudaccount_id") + } + _, err := validators.ValidateModel(userCred, CloudaccountManager, &input.CloudaccountId) + if err != nil { + return input, err + } + input.EntityId = options.Options.ApiServer + if len(input.EntityId) == 0 { + return input, httperrors.NewResourceNotReadyError("not set api_server") + } + input.Name = strings.TrimPrefix(input.EntityId, "https://") + input.Name = strings.TrimPrefix(input.Name, "http://") + + input.MetadataDocument = SamlIdpInstance().GetMetadata(input.CloudaccountId).String() + input.StatusInfrasResourceBaseCreateInput, err = manager.SStatusInfrasResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StatusInfrasResourceBaseCreateInput) + if err != nil { + return input, err + } + return input, nil +} + +func (self *SSAMLProvider) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.StartSAMLProviderCreateTask(ctx, userCred, "") +} + +// 公有云身份提供商列表 +func (manager *SSAMLProviderManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.SAMLProviderListInput) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusInfrasResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusInfrasResourceBaseListInput) + if err != nil { + return nil, err + } + q, err = manager.SCloudaccountResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudaccountResourceListInput) + if err != nil { + return nil, err + } + return q, nil +} + +// 删除 +func (self *SSAMLProvider) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + params := jsonutils.NewDict() + return self.StartSAMLProviderDeleteTask(ctx, userCred, params, "") +} + +func (self *SSAMLProvider) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} + +func (self *SSAMLProvider) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SStatusInfrasResourceBase.Delete(ctx, userCred) +} + +func (self *SSAMLProvider) StartSAMLProviderDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "SAMLProviderDeleteTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.SAML_PROVIDER_STATUS_DELETING, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SSAMLProvider) StartSAMLProviderCreateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "SAMLProviderCreateTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.SAML_PROVIDER_STATUS_CREATING, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SSAMLProvider) syncRemove(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.RealDelete(ctx, userCred) +} + +func (self *SSAMLProvider) SyncWithCloudSAMLProvider(ctx context.Context, userCred mcclient.TokenCredential, ext cloudprovider.ICloudSAMLProvider) error { + _, err := db.Update(self, func() error { + self.ExternalId = ext.GetGlobalId() + self.AuthUrl = ext.GetAuthUrl() + self.Status = ext.GetStatus() + metadata, err := ext.GetMetadataDocument() + if err != nil { + log.Errorf("failed to get metadata for %s error: %v", self.Name, err) + } + if metadata != nil { + self.EntityId = metadata.EntityId + self.MetadataDocument = metadata.String() + } + if self.EntityId != options.Options.ApiServer { + self.Status = api.SAML_PROVIDER_STATUS_NOT_MATCH + } + return nil + }) + return err +} + +func (self *SSAMLProvider) GetMetadataDocument() (samlutils.EntityDescriptor, error) { + return samlutils.ParseMetadata([]byte(self.MetadataDocument)) +} + +func (manager *SSAMLProviderManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.SAMLProviderDetails { + rows := make([]api.SAMLProviderDetails, len(objs)) + infRows := manager.SStatusInfrasResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + acRows := manager.SCloudaccountResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.SAMLProviderDetails{ + StatusInfrasResourceBaseDetails: infRows[i], + CloudaccountResourceDetails: acRows[i], + } + } + return rows +} diff --git a/pkg/cloudid/models/samldriver.go b/pkg/cloudid/models/samldriver.go new file mode 100644 index 0000000000..20e371573c --- /dev/null +++ b/pkg/cloudid/models/samldriver.go @@ -0,0 +1,58 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/samlutils" + "yunion.io/x/onecloud/pkg/util/samlutils/idp" +) + +type SamlInstance func() *idp.SSAMLIdpInstance + +var ( + SamlIdpInstance SamlInstance = nil +) + +type ICloudSAMLLoginDriver interface { + GetEntityID() string + + GetMetadataFilename() string + GetMetadataUrl() string + + GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) + GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccoutId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) +} + +var ( + driverTable = make(map[string]ICloudSAMLLoginDriver) +) + +func Register(driver ICloudSAMLLoginDriver) { + driverTable[driver.GetEntityID()] = driver +} + +func FindDriver(entityId string) ICloudSAMLLoginDriver { + if driver, ok := driverTable[entityId]; ok { + return driver + } + return nil +} + +func AllDrivers() map[string]ICloudSAMLLoginDriver { + return driverTable +} diff --git a/pkg/cloudid/models/samlproviderresource.go b/pkg/cloudid/models/samlproviderresource.go new file mode 100644 index 0000000000..962f9c6503 --- /dev/null +++ b/pkg/cloudid/models/samlproviderresource.go @@ -0,0 +1,32 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import "yunion.io/x/pkg/errors" + +type SAMLProviderResourceBaseManager struct { +} + +type SAMLProviderResourceBase struct { + SAMLProviderId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" json:"saml_provider_id"` +} + +func (self *SAMLProviderResourceBase) GetSAMLProvider() (*SSAMLProvider, error) { + sp, err := SAMLProviderManager.FetchById(self.SAMLProviderId) + if err != nil { + return nil, errors.Wrap(err, "SAMLProviderManager.FetchById") + } + return sp.(*SSAMLProvider), nil +} diff --git a/pkg/cloudid/models/samluser.go b/pkg/cloudid/models/samluser.go new file mode 100644 index 0000000000..ed91c34836 --- /dev/null +++ b/pkg/cloudid/models/samluser.go @@ -0,0 +1,172 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/validators" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SSamluserManager struct { + db.SStatusDomainLevelUserResourceBaseManager + SCloudgroupResourceBaseManager + SCloudaccountResourceBaseManager +} + +var SamluserManager *SSamluserManager + +func init() { + SamluserManager = &SSamluserManager{ + SStatusDomainLevelUserResourceBaseManager: db.NewStatusDomainLevelUserResourceBaseManager( + SSamluser{}, + "samlusers_tbl", + "samluser", + "samlusers", + ), + } + SamluserManager.SetVirtualObject(SamluserManager) +} + +type SSamluser struct { + db.SStatusDomainLevelUserResourceBase + SCloudgroupResourceBase + SCloudaccountResourceBase +} + +func (manager *SSamluserManager) GetResourceCount() ([]db.SScopeResourceCount, error) { + q := manager.Query() + domainCnt, err := db.CalculateResourceCount(q, "domain_id") + if err != nil { + return nil, errors.Wrap(err, "CalculateResourceCount.domain_id") + } + q = manager.Query() + userCnt, err := db.CalculateResourceCount(q, "owner_id") + if err != nil { + return nil, errors.Wrap(err, "CalculateResourceCount.owner_id") + } + return append(domainCnt, userCnt...), nil +} + +func (manager *SSamluserManager) GetIVirtualModelManager() db.IVirtualModelManager { + return manager.GetVirtualObject().(db.IVirtualModelManager) +} + +func (manager *SSamluserManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject { + groupId, _ := data.GetString("cloudgroup_id") + accountId, _ := data.GetString("cloudaccount_id") + return jsonutils.Marshal(map[string]string{"cloudgroup_id": groupId, "cloudaccount_id": accountId}) +} + +func (manager *SSamluserManager) FilterByUniqValues(q *sqlchemy.SQuery, values jsonutils.JSONObject) *sqlchemy.SQuery { + groupId, _ := values.GetString("cloudgroup_id") + accountId, _ := values.GetString("cloudaccount_id") + if len(groupId) > 0 { + q = q.Equals("cloudgroup_id", groupId) + } + if len(accountId) > 0 { + q = q.Equals("cloudaccount_id", accountId) + } + return q +} + +// SAML认证用户列表 +func (manager *SSamluserManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.SamluserListInput) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusDomainLevelUserResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusDomainLevelUserResourceListInput) + if err != nil { + return nil, err + } + q, err = manager.SCloudgroupResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudgroupResourceListInput) + if err != nil { + return nil, err + } + q, err = manager.SCloudaccountResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudaccountResourceListInput) + if err != nil { + return nil, err + } + return q, nil +} + +// 创建SAML认证用户 +func (manager *SSamluserManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.SamluserCreateInput) (api.SamluserCreateInput, error) { + if len(input.OwnerId) > 0 { + user, err := db.UserCacheManager.FetchUserById(ctx, input.OwnerId) + if err != nil { + return input, errors.Wrapf(err, "FetchUserById") + } + input.OwnerId = user.Id + input.Name = user.Name + } else { + input.OwnerId = userCred.GetUserId() + input.Name = userCred.GetUserName() + } + _group, err := validators.ValidateModel(userCred, CloudgroupManager, &input.CloudgroupId) + if err != nil { + return input, err + } + group := _group.(*SCloudgroup) + sq := CloudgroupManager.Query("id").Equals("provider", group.Provider).SubQuery() + q := manager.Query().Equals("owner_id", input.OwnerId).In("cloudgroup_id", sq) + groups := []SCloudgroup{} + err = db.FetchModelObjects(CloudgroupManager, q, &groups) + if err != nil { + return input, httperrors.NewGeneralError(errors.Wrapf(err, "db.FetchModelObjects")) + } + if len(groups) > 0 { + return input, httperrors.NewConflictError("user %s has already in other %s group", input.Name, group.Provider) + } + _account, err := validators.ValidateModel(userCred, CloudaccountManager, &input.CloudaccountId) + if err != nil { + return input, err + } + account := _account.(*SCloudaccount) + if account.Provider != group.Provider { + return input, httperrors.NewConflictError("account %s and group %s not with same provider %s", account.Name, group.Name) + } + input.Status = api.SAML_USER_STATUS_AVAILABLE + return input, nil +} + +func (manager *SSamluserManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.SamluserDetails { + rows := make([]api.SamluserDetails, len(objs)) + userRows := manager.SStatusDomainLevelUserResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + groupRows := manager.SCloudgroupResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + acRows := manager.SCloudaccountResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.SamluserDetails{ + StatusDomainLevelUserResourceDetails: userRows[i], + CloudgroupResourceDetails: groupRows[i], + CloudaccountResourceDetails: acRows[i], + } + } + return rows +} diff --git a/pkg/cloudid/options/options.go b/pkg/cloudid/options/options.go index 640a682d97..742e2f6733 100644 --- a/pkg/cloudid/options/options.go +++ b/pkg/cloudid/options/options.go @@ -23,8 +23,10 @@ type SCloudIdOptions struct { common_options.DBOptions CloudaccountSyncIntervalMinutes int `help:"frequency to sync region cloudaccount task" default:"3"` + SAMLProviderSyncIntervalHours int `help:"frequency to sync account saml provider task" default:"3"` SystemPoliciesSyncIntervalHours int `help:"frequency to sync region cloudaccount task" default:"24"` CloudIdResourceSyncIntervalHours int `help:"frequency to sync region cloudpolicy task" default:"3"` + CloudroleSyncIntervalHours int `help:"frequency to sync region cloudroles task" default:"12"` CloudSAMLMetadataPath string `help:"path to store SAML sp metadata file of cloud providers" default:"/opt/yunion/share/saml/sp-metadata"` } diff --git a/pkg/cloudid/saml/doc.go b/pkg/cloudid/saml/doc.go index 837002ab26..c47e5b0b9b 100644 --- a/pkg/cloudid/saml/doc.go +++ b/pkg/cloudid/saml/doc.go @@ -13,3 +13,109 @@ // limitations under the License. package saml // import "yunion.io/x/onecloud/pkg/cloudid/saml" + +/* + + +-----------------+ +----------------+ + | CloudId Service | | Region Service | + +-----------------+ +----------------+ + + + +----------+ + |EnableSaml| + +-------------------+ +------------+ +----------+ + | Cloudaccount | |Cloudaccount| + | (enabled|disable) | +------------+ +-----------+ + +----________-------+ |DisableSaml| + _______/ \_______ +-----------+ + ___/ \____ + +--------------+ +--------------+ + | SamlProvider | | SamlProvider | + | (available) | | (not match) | + +--------------+ +--------------+ + + + + + + + Saml Check CronJob Task + + +-------+ + | Start | + +---|---+ + | + | + v + +------------------------+ Yes +----------------------------------------+ No +---------------------+ + | Is account enable saml | ----------------> | Is account has available saml provider |----------------> | Create saml provider| + +------------------------+ +----------------------------------------+ +---------------------+ + | | | + | No | Yes | + | | | + v | | + +------+ | | + | End | <-----------------------------------------------<---------------------------------------------------- + +------+ + + + + + + Saml Auth Login + + +-------+ + | Start | + +-------+ + | + | + v + +-----------------------+ Yes +-----------------------------------------------+ + |Is account enable saml |-------------> |Prepare tmp Role and set expired time for user | + +-----------------------+ +-----------------------------------------------+ + | No | + | | + | | + | v + | +------------------+ + | | Auth for console | + | +------------------+ + | | + | | + v | + +------+ | + | End |<---------------------------------------------- + +------+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/ diff --git a/pkg/cloudid/saml/init.go b/pkg/cloudid/saml/init.go index 8e3cd8ddc7..32ad456f18 100644 --- a/pkg/cloudid/saml/init.go +++ b/pkg/cloudid/saml/init.go @@ -20,6 +20,7 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/cloudid/models" "yunion.io/x/onecloud/pkg/cloudid/options" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/util/samlutils" @@ -55,6 +56,7 @@ func initSAMLInstance() error { return errors.Wrap(err, "samlutils.NewSAMLInstance") } + models.SamlIdpInstance = SAMLIdpInstance return nil } @@ -65,6 +67,10 @@ func SAMLInstance() *samlutils.SSAMLInstance { return saml } +func SAMLIdpInstance() *idp.SSAMLIdpInstance { + return idpInstance +} + func IsSAMLEnabled() bool { return saml != nil } diff --git a/pkg/cloudid/saml/initidp.go b/pkg/cloudid/saml/initidp.go index f226da30ec..0031485f24 100644 --- a/pkg/cloudid/saml/initidp.go +++ b/pkg/cloudid/saml/initidp.go @@ -24,8 +24,8 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/cloudid/models" "yunion.io/x/onecloud/pkg/cloudid/options" - "yunion.io/x/onecloud/pkg/cloudid/saml/providers" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/util/httputils" @@ -38,11 +38,11 @@ func initSAMLIdp(app *appsrv.Application, prefix string) error { token := auth.FetchUserCredential(ctx, nil) log.Debugf("Recive SP initiated Login: %s", sp.GetEntityId()) data := samlutils.SSAMLSpInitiatedLoginData{} - driver := providers.FindDriver(sp.GetEntityId()) + driver := models.FindDriver(sp.GetEntityId()) if driver == nil { return data, errors.Wrapf(httperrors.ErrResourceNotFound, "entityID %s not found", sp.GetEntityId()) } - data, err := driver.GetSpInitiatedLoginData(idpId, token.GetUserId(), sp) + data, err := driver.GetSpInitiatedLoginData(ctx, token, idpId, sp) if err != nil { return data, errors.Wrap(err, "driver.GetSpInitiatedLoginData") } @@ -62,11 +62,11 @@ func initSAMLIdp(app *appsrv.Application, prefix string) error { token := auth.FetchUserCredential(ctx, nil) log.Debugf("Recive IDP initiated Login: %s", sp.GetEntityId()) data := samlutils.SSAMLIdpInitiatedLoginData{} - driver := providers.FindDriver(sp.GetEntityId()) + driver := models.FindDriver(sp.GetEntityId()) if driver == nil { return data, errors.Wrapf(httperrors.ErrResourceNotFound, "entityID %s not found", sp.GetEntityId()) } - data, err := driver.GetIdpInitiatedLoginData(idpId, token.GetUserId(), sp) + data, err := driver.GetIdpInitiatedLoginData(ctx, token, idpId, sp) if err != nil { return data, errors.Wrap(err, "driver.GetIdpInitiatedLoginData") } @@ -78,7 +78,7 @@ func initSAMLIdp(app *appsrv.Application, prefix string) error { } idpInst := idp.NewIdpInstance(saml, spFunc, idpFunc, logoutFunc) - for entityId, drvFactory := range providers.AllDrivers() { + for entityId, drvFactory := range models.AllDrivers() { filePath := path.Join(options.Options.CloudSAMLMetadataPath, drvFactory.GetMetadataFilename()) metaBytes, err := ioutil.ReadFile(filePath) if err != nil || len(metaBytes) == 0 { diff --git a/pkg/cloudid/saml/providers/aliyun/driver.go b/pkg/cloudid/saml/providers/aliyun/driver.go index c17cad0020..c853c5efb9 100644 --- a/pkg/cloudid/saml/providers/aliyun/driver.go +++ b/pkg/cloudid/saml/providers/aliyun/driver.go @@ -15,13 +15,16 @@ package aliyun import ( + "context" + "yunion.io/x/pkg/errors" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/samlutils" "yunion.io/x/onecloud/pkg/util/samlutils/idp" ) -func (d *SAliyunSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) { +func (d *SAliyunSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) { // TODO data := samlutils.SSAMLIdpInitiatedLoginData{} data.NameId = "ecsossreadonly" @@ -41,7 +44,7 @@ func (d *SAliyunSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userI return data, nil } -func (d *SAliyunSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) { +func (d *SAliyunSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) { // not supported return samlutils.SSAMLSpInitiatedLoginData{}, errors.ErrNotSupported } diff --git a/pkg/cloudid/saml/providers/aliyun/factory.go b/pkg/cloudid/saml/providers/aliyun/factory.go index bf5f194459..9573953a81 100644 --- a/pkg/cloudid/saml/providers/aliyun/factory.go +++ b/pkg/cloudid/saml/providers/aliyun/factory.go @@ -15,7 +15,7 @@ package aliyun import ( - "yunion.io/x/onecloud/pkg/cloudid/saml/providers" + "yunion.io/x/onecloud/pkg/cloudid/models" "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -34,5 +34,5 @@ func (d *SAliyunSAMLDriver) GetMetadataUrl() string { } func init() { - providers.Register(&SAliyunSAMLDriver{}) + models.Register(&SAliyunSAMLDriver{}) } diff --git a/pkg/cloudid/saml/providers/aws/driver.go b/pkg/cloudid/saml/providers/aws/driver.go index bfae216057..5c1e8ca7ce 100644 --- a/pkg/cloudid/saml/providers/aws/driver.go +++ b/pkg/cloudid/saml/providers/aws/driver.go @@ -15,17 +15,49 @@ package aws import ( + "context" + "database/sql" + "fmt" + "yunion.io/x/pkg/errors" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/samlutils" "yunion.io/x/onecloud/pkg/util/samlutils/idp" ) -func (d *SAWSSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) { - // TODO +func (d *SAWSSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) { data := samlutils.SSAMLIdpInitiatedLoginData{} - data.NameId = "ec2s3readonly" + _account, err := models.CloudaccountManager.FetchById(cloudAccountId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return data, httperrors.NewResourceNotFoundError("cloudaccount", cloudAccountId) + } + return data, httperrors.NewGeneralError(err) + } + account := _account.(*models.SCloudaccount) + if account.Provider != api.CLOUD_PROVIDER_AWS { + return data, httperrors.NewClientError("cloudaccount %s is %s not %s", account.Id, account.Provider, api.CLOUD_PROVIDER_AWS) + } + if account.SAMLAuth.IsFalse() { + return data, httperrors.NewNotSupportedError("cloudaccount %s not open saml auth", account.Id) + } + + SAMLProvider, valid := account.IsSAMLProviderValid() + if !valid { + return data, httperrors.NewResourceNotReadyError("SAMLProvider for account %s not ready", account.Id) + } + + role, err := account.SyncRole(userCred.GetUserId()) + if err != nil { + return data, httperrors.NewGeneralError(errors.Wrapf(err, "SyncRole")) + } + + data.NameId = userCred.GetUserName() data.NameIdFormat = samlutils.NAME_ID_FORMAT_PERSISTENT data.AudienceRestriction = "https://signin.aws.amazon.com/saml" for _, v := range []struct { @@ -36,17 +68,17 @@ func (d *SAWSSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId s { name: "https://aws.amazon.com/SAML/Attributes/Role", friendlyName: "RoleEntitlement", - value: "arn:aws:iam::285906155448:role/ec2s3readonly,arn:aws:iam::285906155448:saml-provider/saml.yunion.cn", + value: fmt.Sprintf("%s,%s", role.ExternalId, SAMLProvider.ExternalId), }, { name: "https://aws.amazon.com/SAML/Attributes/RoleSessionName", friendlyName: "RoleSessionName", - value: "ec2s3readonly", + value: userCred.GetUserId(), }, { name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.3", friendlyName: "eduPersonOrgDN", - value: "ec2s3readonly", + value: userCred.GetUserName(), }, } { data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{ @@ -60,7 +92,7 @@ func (d *SAWSSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId s return data, nil } -func (d *SAWSSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) { +func (d *SAWSSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) { // not supported return samlutils.SSAMLSpInitiatedLoginData{}, errors.ErrNotSupported } diff --git a/pkg/cloudid/saml/providers/aws/factory.go b/pkg/cloudid/saml/providers/aws/factory.go index 1d1818054c..4123c37529 100644 --- a/pkg/cloudid/saml/providers/aws/factory.go +++ b/pkg/cloudid/saml/providers/aws/factory.go @@ -15,7 +15,7 @@ package aws import ( - "yunion.io/x/onecloud/pkg/cloudid/saml/providers" + "yunion.io/x/onecloud/pkg/cloudid/models" "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -34,5 +34,5 @@ func (d *SAWSSAMLDriver) GetMetadataUrl() string { } func init() { - providers.Register(&SAWSSAMLDriver{}) + models.Register(&SAWSSAMLDriver{}) } diff --git a/pkg/cloudid/saml/providers/awscn/driver.go b/pkg/cloudid/saml/providers/awscn/driver.go index 8e3459b677..24475ec06e 100644 --- a/pkg/cloudid/saml/providers/awscn/driver.go +++ b/pkg/cloudid/saml/providers/awscn/driver.go @@ -15,17 +15,48 @@ package awscn import ( + "context" + "database/sql" + "fmt" + "yunion.io/x/pkg/errors" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/samlutils" "yunion.io/x/onecloud/pkg/util/samlutils/idp" ) -func (d *SAWSCNSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) { - // TODO +func (d *SAWSCNSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) { data := samlutils.SSAMLIdpInitiatedLoginData{} + _account, err := models.CloudaccountManager.FetchById(cloudAccountId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return data, httperrors.NewResourceNotFoundError("cloudaccount", cloudAccountId) + } + return data, httperrors.NewGeneralError(err) + } + account := _account.(*models.SCloudaccount) + if account.Provider != api.CLOUD_PROVIDER_AWS { + return data, httperrors.NewClientError("cloudaccount %s is %s not %s", account.Id, account.Provider, api.CLOUD_PROVIDER_AWS) + } + if account.SAMLAuth.IsFalse() { + return data, httperrors.NewNotSupportedError("cloudaccount %s not open saml auth", account.Id) + } - data.NameId = "ec2s3readonly" + SAMLProvider, valid := account.IsSAMLProviderValid() + if !valid { + return data, httperrors.NewResourceNotReadyError("SAMLProvider for account %s not ready", account.Id) + } + + role, err := account.SyncRole(userCred.GetUserId()) + if err != nil { + return data, httperrors.NewGeneralError(errors.Wrapf(err, "SyncRole")) + } + + data.NameId = userCred.GetUserName() data.NameIdFormat = samlutils.NAME_ID_FORMAT_PERSISTENT data.AudienceRestriction = "https://signin.amazonaws.cn/saml" for _, v := range []struct { @@ -36,17 +67,17 @@ func (d *SAWSCNSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId { name: "https://aws.amazon.com/SAML/Attributes/Role", friendlyName: "RoleEntitlement", - value: "arn:aws-cn:iam::248697896586:role/ec2s3readonly,arn:aws-cn:iam::248697896586:saml-provider/saml.yunion.io", + value: fmt.Sprintf("%s,%s", role.ExternalId, SAMLProvider.ExternalId), }, { name: "https://aws.amazon.com/SAML/Attributes/RoleSessionName", friendlyName: "RoleSessionName", - value: "ec2s3readonly", + value: userCred.GetUserId(), }, { name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.3", friendlyName: "eduPersonOrgDN", - value: "ec2s3readonly", + value: userCred.GetUserName(), }, } { data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{ @@ -60,7 +91,7 @@ func (d *SAWSCNSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId return data, nil } -func (d *SAWSCNSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) { +func (d *SAWSCNSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) { // not supported return samlutils.SSAMLSpInitiatedLoginData{}, errors.ErrNotSupported } diff --git a/pkg/cloudid/saml/providers/awscn/factory.go b/pkg/cloudid/saml/providers/awscn/factory.go index 473fb57a67..b59fba16e7 100644 --- a/pkg/cloudid/saml/providers/awscn/factory.go +++ b/pkg/cloudid/saml/providers/awscn/factory.go @@ -15,7 +15,7 @@ package awscn import ( - "yunion.io/x/onecloud/pkg/cloudid/saml/providers" + "yunion.io/x/onecloud/pkg/cloudid/models" "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -34,5 +34,5 @@ func (d *SAWSCNSAMLDriver) GetMetadataUrl() string { } func init() { - providers.Register(&SAWSCNSAMLDriver{}) + models.Register(&SAWSCNSAMLDriver{}) } diff --git a/pkg/cloudid/saml/providers/google/driver.go b/pkg/cloudid/saml/providers/google/driver.go index 5778b5a7a0..e01411255c 100644 --- a/pkg/cloudid/saml/providers/google/driver.go +++ b/pkg/cloudid/saml/providers/google/driver.go @@ -15,19 +15,22 @@ package google import ( + "context" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/samlutils" "yunion.io/x/onecloud/pkg/util/samlutils/idp" ) -func (d *SGoogleSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) { +func (d *SGoogleSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) { // not supported data := samlutils.SSAMLIdpInitiatedLoginData{} return data, httperrors.ErrNotSupported } -func (d *SGoogleSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) { +func (d *SGoogleSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) { // TODO data := samlutils.SSAMLSpInitiatedLoginData{} diff --git a/pkg/cloudid/saml/providers/google/factory.go b/pkg/cloudid/saml/providers/google/factory.go index a058417a4a..f87da34e5f 100644 --- a/pkg/cloudid/saml/providers/google/factory.go +++ b/pkg/cloudid/saml/providers/google/factory.go @@ -15,7 +15,7 @@ package google import ( - "yunion.io/x/onecloud/pkg/cloudid/saml/providers" + "yunion.io/x/onecloud/pkg/cloudid/models" "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -34,5 +34,5 @@ func (d *SGoogleSAMLDriver) GetMetadataUrl() string { } func init() { - providers.Register(&SGoogleSAMLDriver{}) + models.Register(&SGoogleSAMLDriver{}) } diff --git a/pkg/cloudid/saml/providers/huawei/driver.go b/pkg/cloudid/saml/providers/huawei/driver.go index ca773d1bff..3751d95a08 100644 --- a/pkg/cloudid/saml/providers/huawei/driver.go +++ b/pkg/cloudid/saml/providers/huawei/driver.go @@ -15,33 +15,68 @@ package huawei import ( + "context" + "database/sql" + + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudid/models" "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/samlutils" "yunion.io/x/onecloud/pkg/util/samlutils/idp" ) -func (d *SHuaweiSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) { +func (d *SHuaweiSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) { // not supported data := samlutils.SSAMLIdpInitiatedLoginData{} return data, httperrors.ErrNotSupported } -func (d *SHuaweiSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) { - // TODO +func (d *SHuaweiSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) { data := samlutils.SSAMLSpInitiatedLoginData{} - data.NameId = "yunionoss" + _account, err := models.CloudaccountManager.FetchById(cloudAccountId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return data, httperrors.NewResourceNotFoundError("cloudaccount", cloudAccountId) + } + return data, httperrors.NewGeneralError(err) + } + account := _account.(*models.SCloudaccount) + if account.Provider != api.CLOUD_PROVIDER_HUAWEI { + return data, httperrors.NewClientError("cloudaccount %s is %s not %s", account.Id, account.Provider, api.CLOUD_PROVIDER_HUAWEI) + } + if account.SAMLAuth.IsFalse() { + return data, httperrors.NewNotSupportedError("cloudaccount %s not open saml auth", account.Id) + } + + _, valid := account.IsSAMLProviderValid() + if !valid { + return data, httperrors.NewResourceNotReadyError("SAMLProvider for account %s not ready", account.Id) + } + + groups, err := account.GetUserCloudgroups(userCred.GetUserId()) + if err != nil { + return data, httperrors.NewGeneralError(errors.Wrapf(err, "GetUserCloudgroups")) + } + if len(groups) == 0 { + return data, httperrors.NewResourceNotFoundError("no available group found") + } + + data.NameId = userCred.GetUserName() data.NameIdFormat = samlutils.NAME_ID_FORMAT_TRANSIENT data.AudienceRestriction = sp.GetEntityId() - for k, v := range map[string]string{ - "User": "ec2admin", - "Group": "ec2admin", + for k, v := range map[string][]string{ + "User": []string{userCred.GetUserName()}, + "Groups": groups, } { data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{ Name: k, FriendlyName: k, NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", - Values: []string{v}, + Values: v, }) } diff --git a/pkg/cloudid/saml/providers/huawei/factory.go b/pkg/cloudid/saml/providers/huawei/factory.go index 83efab788f..046c909f49 100644 --- a/pkg/cloudid/saml/providers/huawei/factory.go +++ b/pkg/cloudid/saml/providers/huawei/factory.go @@ -15,7 +15,7 @@ package huawei import ( - "yunion.io/x/onecloud/pkg/cloudid/saml/providers" + "yunion.io/x/onecloud/pkg/cloudid/models" "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -34,5 +34,5 @@ func (d *SHuaweiSAMLDriver) GetMetadataUrl() string { } func init() { - providers.Register(&SHuaweiSAMLDriver{}) + models.Register(&SHuaweiSAMLDriver{}) } diff --git a/pkg/cloudid/saml/providers/qcloud/driver.go b/pkg/cloudid/saml/providers/qcloud/driver.go index f6ec7b7d17..6999a4f44d 100644 --- a/pkg/cloudid/saml/providers/qcloud/driver.go +++ b/pkg/cloudid/saml/providers/qcloud/driver.go @@ -15,15 +15,51 @@ package qcloud import ( + "context" + "database/sql" + "fmt" + + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/samlutils" "yunion.io/x/onecloud/pkg/util/samlutils/idp" ) -func (d *SQcloudSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) { - // TODO +func (d *SQcloudSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) { data := samlutils.SSAMLIdpInitiatedLoginData{} - data.NameId = "cvmcosreadonly" + _account, err := models.CloudaccountManager.FetchById(cloudAccountId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return data, httperrors.NewResourceNotFoundError("cloudaccount", cloudAccountId) + } + return data, httperrors.NewGeneralError(err) + } + account := _account.(*models.SCloudaccount) + if account.Provider != api.CLOUD_PROVIDER_QCLOUD { + return data, httperrors.NewClientError("cloudaccount %s is %s not %s", account.Id, account.Provider, api.CLOUD_PROVIDER_QCLOUD) + } + if account.SAMLAuth.IsFalse() { + return data, httperrors.NewNotSupportedError("cloudaccount %s not open saml auth", account.Id) + } + + SAMLProvider, valid := account.IsSAMLProviderValid() + if !valid { + return data, httperrors.NewResourceNotReadyError("SAMLProvider for account %s not ready", account.Id) + } + + role, err := account.SyncRole(userCred.GetUserId()) + if err != nil { + return data, httperrors.NewGeneralError(errors.Wrapf(err, "SyncRole")) + } + + roleStr := fmt.Sprintf("qcs::cam::uin/%s:roleName/%s,qcs::cam::uin/%s:saml-provider/%s", account.AccountId, role.ExternalId, account.AccountId, SAMLProvider.ExternalId) + + data.NameId = role.Name data.NameIdFormat = samlutils.NAME_ID_FORMAT_TRANSIENT data.AudienceRestriction = "https://cloud.tencent.com" for _, v := range []struct { @@ -34,12 +70,12 @@ func (d *SQcloudSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userI { name: "https://cloud.tencent.com/SAML/Attributes/Role", friendlyName: "RoleEntitlement", - value: "qcs::cam::uin/100008182714:roleName/cvmcosreadonly,qcs::cam::uin/100008182714:saml-provider/saml.yunion.io", + value: roleStr, }, { name: "https://cloud.tencent.com/SAML/Attributes/RoleSessionName", friendlyName: "RoleSessionName", - value: "cvmcosreadonly", + value: role.Name, }, } { data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{ @@ -53,11 +89,36 @@ func (d *SQcloudSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userI return data, nil } -func (d *SQcloudSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) { - // not supported +func (d *SQcloudSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) { data := samlutils.SSAMLSpInitiatedLoginData{} + _account, err := models.CloudaccountManager.FetchById(cloudAccountId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return data, httperrors.NewResourceNotFoundError("cloudaccount", cloudAccountId) + } + return data, httperrors.NewGeneralError(err) + } + account := _account.(*models.SCloudaccount) + if account.Provider != api.CLOUD_PROVIDER_QCLOUD { + return data, httperrors.NewClientError("cloudaccount %s is %s not %s", account.Id, account.Provider, api.CLOUD_PROVIDER_QCLOUD) + } + if account.SAMLAuth.IsFalse() { + return data, httperrors.NewNotSupportedError("cloudaccount %s not open saml auth", account.Id) + } - data.NameId = "cvmcosreadonly" + SAMLProvider, valid := account.IsSAMLProviderValid() + if !valid { + return data, httperrors.NewResourceNotReadyError("SAMLProvider for account %s not ready", account.Id) + } + + role, err := account.SyncRole(userCred.GetUserId()) + if err != nil { + return data, httperrors.NewGeneralError(errors.Wrapf(err, "SyncRole")) + } + + roleStr := fmt.Sprintf("qcs::cam::uin/%s:roleName/%s,qcs::cam::uin/%s:saml-provider/%s", account.AccountId, role.ExternalId, account.AccountId, SAMLProvider.ExternalId) + + data.NameId = role.Name data.NameIdFormat = samlutils.NAME_ID_FORMAT_TRANSIENT data.AudienceRestriction = "https://cloud.tencent.com" for _, v := range []struct { @@ -68,12 +129,12 @@ func (d *SQcloudSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId { name: "https://cloud.tencent.com/SAML/Attributes/Role", friendlyName: "RoleEntitlement", - value: "qcs::cam::uin/100008182714:roleName/cvmcosreadonly,qcs::cam::uin/100008182714:saml-provider/saml.yunion.io", + value: roleStr, }, { name: "https://cloud.tencent.com/SAML/Attributes/RoleSessionName", friendlyName: "RoleSessionName", - value: "cvmcosreadonly", + value: role.Name, }, } { data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{ diff --git a/pkg/cloudid/saml/providers/qcloud/factory.go b/pkg/cloudid/saml/providers/qcloud/factory.go index a6df41cf0f..ef456de108 100644 --- a/pkg/cloudid/saml/providers/qcloud/factory.go +++ b/pkg/cloudid/saml/providers/qcloud/factory.go @@ -15,7 +15,7 @@ package qcloud import ( - "yunion.io/x/onecloud/pkg/cloudid/saml/providers" + "yunion.io/x/onecloud/pkg/cloudid/models" "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -34,5 +34,5 @@ func (d *SQcloudSAMLDriver) GetMetadataUrl() string { } func init() { - providers.Register(&SQcloudSAMLDriver{}) + models.Register(&SQcloudSAMLDriver{}) } diff --git a/pkg/cloudid/service/handlers.go b/pkg/cloudid/service/handlers.go index fb60fdaf46..7cd593996d 100644 --- a/pkg/cloudid/service/handlers.go +++ b/pkg/cloudid/service/handlers.go @@ -65,6 +65,9 @@ func InitHandlers(app *appsrv.Application) { models.CloudgroupcacheManager, models.CloudpolicyManager, models.CloudpolicycacheManager, + models.SAMLProviderManager, + models.CloudroleManager, + models.SamluserManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/cloudid/service/service.go b/pkg/cloudid/service/service.go index 8edd05dace..ba2f3b23f7 100644 --- a/pkg/cloudid/service/service.go +++ b/pkg/cloudid/service/service.go @@ -61,8 +61,10 @@ func StartService() { if !opts.IsSlaveNode { cron := cronman.InitCronJobManager(true, options.Options.CronJobWorkerCount) cron.AddJobAtIntervalsWithStartRun("SyncCloudaccounts", time.Duration(opts.CloudaccountSyncIntervalMinutes)*time.Minute, models.CloudaccountManager.SyncCloudaccounts, true) + cron.AddJobAtIntervalsWithStartRun("SyncSAMLProviders", time.Duration(opts.SAMLProviderSyncIntervalHours)*time.Hour, models.CloudaccountManager.SyncSAMLProviders, true) cron.AddJobAtIntervalsWithStartRun("SyncSystemCloudpolicies", time.Duration(opts.SystemPoliciesSyncIntervalHours)*time.Hour, models.CloudaccountManager.SyncCloudidSystemPolicies, true) cron.AddJobAtIntervalsWithStartRun("SyncCloudIdResources", time.Duration(opts.CloudIdResourceSyncIntervalHours)*time.Hour, models.CloudaccountManager.SyncCloudidResources, true) + cron.AddJobAtIntervalsWithStartRun("SyncCloudroles", time.Duration(opts.CloudroleSyncIntervalHours)*time.Hour, models.CloudaccountManager.SyncCloudroles, true) cron.Start() defer cron.Stop() } diff --git a/pkg/cloudid/tasks/cloud_role_delete_task.go b/pkg/cloudid/tasks/cloud_role_delete_task.go new file mode 100644 index 0000000000..c0faa9dfba --- /dev/null +++ b/pkg/cloudid/tasks/cloud_role_delete_task.go @@ -0,0 +1,82 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type CloudroleDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(CloudroleDeleteTask{}) +} + +func (self *CloudroleDeleteTask) taskFailed(ctx context.Context, role *models.SCloudrole, err error) { + role.SetStatus(self.GetUserCred(), api.CLOUD_ROLE_STATUS_DELETE_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, role, logclient.ACT_DELETE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *CloudroleDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + role := obj.(*models.SCloudrole) + + isPurge := jsonutils.QueryBoolean(self.Params, "purge", false) + if len(role.ExternalId) == 0 || isPurge { + role.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) + return + } + + account, err := role.GetCloudaccount() + if err != nil { + self.taskFailed(ctx, role, errors.Wrapf(err, "GetCloudaccount")) + return + } + provider, err := account.GetProvider() + if err != nil { + self.taskFailed(ctx, role, errors.Wrapf(err, "GetProvider")) + return + } + iRole, err := provider.GetICloudroleById(role.ExternalId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + role.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) + return + } + self.taskFailed(ctx, role, errors.Wrapf(err, "GetICloudroleById(%s)", role.ExternalId)) + return + } + err = iRole.Delete() + if err != nil { + self.taskFailed(ctx, role, errors.Wrapf(err, "iRole.Delete")) + return + } + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/saml_provider_create_task.go b/pkg/cloudid/tasks/saml_provider_create_task.go new file mode 100644 index 0000000000..1592326306 --- /dev/null +++ b/pkg/cloudid/tasks/saml_provider_create_task.go @@ -0,0 +1,92 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "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/cloudid/models" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type SAMLProviderCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(SAMLProviderCreateTask{}) +} + +func (self *SAMLProviderCreateTask) taskFailed(ctx context.Context, saml *models.SSAMLProvider, err error) { + saml.SetStatus(self.GetUserCred(), api.SAML_PROVIDER_STATUS_CREATE_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, saml, logclient.ACT_CREATE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *SAMLProviderCreateTask) taskComplete(ctx context.Context, saml *models.SSAMLProvider) { + self.SetStageComplete(ctx, nil) +} + +func (self *SAMLProviderCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + saml := obj.(*models.SSAMLProvider) + + metadata, err := saml.GetMetadataDocument() + if err != nil { + self.taskFailed(ctx, saml, errors.Wrapf(err, "GetMetadataDocument")) + return + } + + account, err := saml.GetCloudaccount() + if err != nil { + self.taskFailed(ctx, saml, errors.Wrapf(err, "GetCloudaccount")) + return + } + + provider, err := account.GetProvider() + if err != nil { + self.taskFailed(ctx, saml, errors.Wrap(err, "GetProvider")) + return + } + + opts := cloudprovider.SAMLProviderCreateOptions{ + Name: saml.Name, + Metadata: metadata, + } + + lockman.LockRawObject(ctx, saml.CloudaccountId, "saml-provider") + defer lockman.ReleaseRawObject(ctx, saml.CloudaccountId, "saml-provider") + + iSAMLProvider, err := provider.CreateICloudSAMLProvider(&opts) + if err != nil { + self.taskFailed(ctx, saml, errors.Wrapf(err, "CreateICloudSAMLProvider")) + return + } + + err = saml.SyncWithCloudSAMLProvider(ctx, self.GetUserCred(), iSAMLProvider) + if err != nil { + self.taskFailed(ctx, saml, errors.Wrap(err, "SyncWithCloudSAMLProvider")) + return + } + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/saml_provider_delete_task.go b/pkg/cloudid/tasks/saml_provider_delete_task.go new file mode 100644 index 0000000000..fc02d41f91 --- /dev/null +++ b/pkg/cloudid/tasks/saml_provider_delete_task.go @@ -0,0 +1,85 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type SAMLProviderDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(SAMLProviderDeleteTask{}) +} + +func (self *SAMLProviderDeleteTask) taskFailed(ctx context.Context, saml *models.SSAMLProvider, err error) { + saml.SetStatus(self.GetUserCred(), api.SAML_PROVIDER_STATUS_DELETE_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, saml, logclient.ACT_DELETE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *SAMLProviderDeleteTask) taskComplete(ctx context.Context, saml *models.SSAMLProvider) { + saml.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} + +func (self *SAMLProviderDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + saml := obj.(*models.SSAMLProvider) + + if len(saml.ExternalId) == 0 { + self.taskComplete(ctx, saml) + return + } + + account, err := saml.GetCloudaccount() + if err != nil { + self.taskFailed(ctx, saml, errors.Wrapf(err, "GetCloudaccount")) + return + } + + provider, err := account.GetProvider() + if err != nil { + self.taskFailed(ctx, saml, errors.Wrapf(err, "GetProvider")) + return + } + samls, err := provider.GetICloudSAMLProviders() + if err != nil { + self.taskFailed(ctx, saml, errors.Wrapf(err, "GetICloudSAMLProviders")) + return + } + + for i := range samls { + if samls[i].GetGlobalId() == saml.ExternalId { + err = samls[i].Delete() + if err != nil { + self.taskFailed(ctx, saml, errors.Wrapf(err, "Delete")) + return + } + } + } + + self.taskComplete(ctx, saml) +} diff --git a/pkg/cloudid/tasks/sync_cloudroles_task.go b/pkg/cloudid/tasks/sync_cloudroles_task.go new file mode 100644 index 0000000000..4b41c13f71 --- /dev/null +++ b/pkg/cloudid/tasks/sync_cloudroles_task.go @@ -0,0 +1,59 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" +) + +type SyncCloudrolesTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(SyncCloudrolesTask{}) +} + +func (self *SyncCloudrolesTask) taskFailed(ctx context.Context, account *models.SCloudaccount, err error) { + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *SyncCloudrolesTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + account := obj.(*models.SCloudaccount) + + provider, err := account.GetProvider() + if err != nil { + self.taskFailed(ctx, account, errors.Wrapf(err, "GetProvider")) + return + } + + roles, err := provider.GetICloudroles() + if err != nil { + self.taskFailed(ctx, account, errors.Wrapf(err, "GetICloudroles")) + return + } + result := account.SyncCloudroles(ctx, self.GetUserCred(), roles) + log.Infof("SyncCloudroles for account %s(%s) result: %s", account.Name, account.Provider, result.Result()) + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/sync_saml_providers_task.go b/pkg/cloudid/tasks/sync_saml_providers_task.go new file mode 100644 index 0000000000..993003fe05 --- /dev/null +++ b/pkg/cloudid/tasks/sync_saml_providers_task.go @@ -0,0 +1,64 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "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/cloudid/models" +) + +type SyncSAMLProvidersTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(SyncSAMLProvidersTask{}) +} + +func (self *SyncSAMLProvidersTask) taskFailed(ctx context.Context, account *models.SCloudaccount, err error) { + log.Errorf("failed sync saml provider for account %s error: %v", account.Name, err) + self.SetStageFailed(ctx, nil) +} + +func (self *SyncSAMLProvidersTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + account := obj.(*models.SCloudaccount) + + provider, err := account.GetProvider() + if err != nil { + self.taskFailed(ctx, account, errors.Wrapf(err, "GetProvider")) + return + } + + lockman.LockRawObject(ctx, account.Id, "saml-provider") + defer lockman.ReleaseRawObject(ctx, account.Id, "saml-provider") + + samls, err := provider.GetICloudSAMLProviders() + if err != nil { + self.taskFailed(ctx, account, errors.Wrapf(err, "GetICloudSAMLProviders")) + return + } + + result := account.SyncSAMLProviders(ctx, self.GetUserCred(), samls) + log.Infof("Sync SAMLProviders for account %s(%s) result: %s", account.Name, account.Provider, result.Result()) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudprovider/cloudprovider.go b/pkg/cloudprovider/cloudprovider.go index a893e0d009..530aea6acd 100644 --- a/pkg/cloudprovider/cloudprovider.go +++ b/pkg/cloudprovider/cloudprovider.go @@ -153,6 +153,8 @@ type ProviderConfig struct { Account string Secret string + AccountId string + ProxyFunc httputils.TransportProxyFunc } @@ -223,6 +225,8 @@ type ICloudProviderFactory interface { GetSupportedDnsPolicyTypes() map[TDnsZoneType][]TDnsPolicyType GetSupportedDnsPolicyValues() map[TDnsPolicyType][]TDnsPolicyValue GetTTLRange(zoneType TDnsZoneType, productType TDnsProductType) TTlRange + + IsSupportSAMLAuth() bool } type ICloudProvider interface { @@ -264,6 +268,12 @@ type ICloudProvider interface { CreateICloudgroup(name, desc string) (ICloudgroup, error) GetIClouduserByName(name string) (IClouduser, error) CreateIClouduser(conf *SClouduserCreateConfig) (IClouduser, error) + CreateICloudSAMLProvider(opts *SAMLProviderCreateOptions) (ICloudSAMLProvider, error) + GetICloudSAMLProviders() ([]ICloudSAMLProvider, error) + GetICloudroles() ([]ICloudrole, error) + GetICloudroleById(id string) (ICloudrole, error) + GetICloudroleByName(name string) (ICloudrole, error) + CreateICloudrole(opts *SRoleCreateOptions) (ICloudrole, error) CreateICloudpolicy(opts *SCloudpolicyCreateOptions) (ICloudpolicy, error) @@ -271,7 +281,6 @@ type ICloudProvider interface { CreateSubscription(SubscriptionCreateInput) error GetSamlEntityId() string - GetSamlSpInitiatedLoginUrl(idpName string) string GetICloudDnsZones() ([]ICloudDnsZone, error) GetICloudDnsZoneById(id string) (ICloudDnsZone, error) @@ -428,6 +437,30 @@ func (self *SBaseProvider) CreateIClouduser(conf *SClouduserCreateConfig) (IClou return nil, ErrNotImplemented } +func (self *SBaseProvider) GetICloudSAMLProviders() ([]ICloudSAMLProvider, error) { + return nil, errors.Wrapf(ErrNotImplemented, "GetICloudSAMLProviders") +} + +func (self *SBaseProvider) GetICloudroles() ([]ICloudrole, error) { + return nil, errors.Wrapf(ErrNotImplemented, "GetICloudroles") +} + +func (self *SBaseProvider) GetICloudroleById(id string) (ICloudrole, error) { + return nil, errors.Wrapf(ErrNotImplemented, "GetICloudroleById") +} + +func (self *SBaseProvider) GetICloudroleByName(name string) (ICloudrole, error) { + return nil, errors.Wrapf(ErrNotImplemented, "GetICloudroleByName") +} + +func (self *SBaseProvider) CreateICloudrole(opts *SRoleCreateOptions) (ICloudrole, error) { + return nil, errors.Wrapf(ErrNotImplemented, "CreateICloudrole") +} + +func (self *SBaseProvider) CreateICloudSAMLProvider(opts *SAMLProviderCreateOptions) (ICloudSAMLProvider, error) { + return nil, errors.Wrapf(ErrNotImplemented, "CreateICloudSAMLProvider") +} + func (self *SBaseProvider) CreateICloudpolicy(opts *SCloudpolicyCreateOptions) (ICloudpolicy, error) { return nil, ErrNotImplemented } @@ -464,10 +497,6 @@ func (self *SBaseProvider) GetSamlEntityId() string { return "" } -func (self *SBaseProvider) GetSamlSpInitiatedLoginUrl(idpName string) string { - return "" -} - func NewBaseProvider(factory ICloudProviderFactory) SBaseProvider { return SBaseProvider{factory: factory} } @@ -557,6 +586,10 @@ func (factory *baseProviderFactory) GetSupportedBrands() []string { return []string{} } +func (factory *baseProviderFactory) IsSupportSAMLAuth() bool { + return false +} + func (factory *baseProviderFactory) GetProvider(providerId, providerName, url, username, password string) (ICloudProvider, error) { return nil, httperrors.NewNotImplementedError("Not Implemented GetProvider") } diff --git a/pkg/cloudprovider/cloudrole.go b/pkg/cloudprovider/cloudrole.go new file mode 100644 index 0000000000..e6ffccf330 --- /dev/null +++ b/pkg/cloudprovider/cloudrole.go @@ -0,0 +1,21 @@ +// 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 cloudprovider + +type SRoleCreateOptions struct { + Name string + Desc string + SAMLProvider string +} diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 65b6353d27..25cf6eb0b1 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -24,6 +24,7 @@ import ( "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/billing" "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/util/samlutils" ) type ICloudResource interface { @@ -1101,6 +1102,28 @@ type ICloudVpcPeeringConnection interface { GetPeerVpcId() string GetPeerAccountId() string GetEnabled() bool + Delete() error +} + +type ICloudSAMLProvider interface { + ICloudResource + + GetMetadataDocument() (*samlutils.EntityDescriptor, error) + + GetAuthUrl() string + Delete() error +} + +type ICloudrole interface { + GetGlobalId() string + GetName() string + + GetDocument() *jsonutils.JSONDict + GetSAMLProvider() string + + GetICloudpolicies() ([]ICloudpolicy, error) + AttachPolicy(id string) error + DetachPolicy(id string) error Delete() error } diff --git a/pkg/cloudprovider/saml.go b/pkg/cloudprovider/saml.go index 30012203d7..469c12f119 100644 --- a/pkg/cloudprovider/saml.go +++ b/pkg/cloudprovider/saml.go @@ -14,6 +14,8 @@ package cloudprovider +import "yunion.io/x/onecloud/pkg/util/samlutils" + const ( SAML_ENTITY_ID_ALIYUN_ROLE = "urn:alibaba:cloudcomputing" SAML_ENTITY_ID_AWS_CN = "urn:amazon:webservices:cn-north-1" @@ -22,3 +24,8 @@ const ( SAML_ENTITY_ID_HUAWEI_CLOUD = "https://auth.huaweicloud.com/" SAML_ENTITY_ID_GOOGLE = "google.com" ) + +type SAMLProviderCreateOptions struct { + Name string + Metadata samlutils.EntityDescriptor +} diff --git a/pkg/compute/models/cloudaccount_saml.go b/pkg/compute/models/cloudaccount_saml.go index 0a623990bd..4b97840e53 100644 --- a/pkg/compute/models/cloudaccount_saml.go +++ b/pkg/compute/models/cloudaccount_saml.go @@ -16,7 +16,6 @@ package models import ( "context" - "fmt" "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" @@ -27,8 +26,9 @@ import ( "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/util/httputils" - "yunion.io/x/onecloud/pkg/util/samlutils" ) func (account *SCloudaccount) AllowGetDetailsSaml(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { @@ -38,6 +38,10 @@ func (account *SCloudaccount) AllowGetDetailsSaml(ctx context.Context, userCred func (account *SCloudaccount) GetDetailsSaml(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (api.GetCloudaccountSamlOutput, error) { output := api.GetCloudaccountSamlOutput{} + if account.SAMLAuth.IsFalse() { + return output, httperrors.NewNotSupportedError("account %s not enable saml auth", account.Name) + } + provider, err := account.GetProvider() if err != nil { return output, errors.Wrap(err, "GetProviderFactory") @@ -50,18 +54,19 @@ func (account *SCloudaccount) GetDetailsSaml(ctx context.Context, userCred mccli output.RedirectLoginUrl = httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, "redirect/login", account.Id) output.RedirectLogoutUrl = httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, "redirect/logout", account.Id) output.MetadataUrl = httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, "metadata", account.Id) - // XXXXX - // TODO, find idpName for this cloudaccount - // XXXXX - idpName := "saml.yunion.io" - output.InitLoginUrl = provider.GetSamlSpInitiatedLoginUrl(idpName) - if len(output.InitLoginUrl) == 0 { - input := samlutils.SIdpInitiatedLoginInput{ - EntityID: output.EntityId, - IdpId: account.Id, - } - output.InitLoginUrl = httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, fmt.Sprintf("sso?%s", jsonutils.Marshal(input).QueryString())) + s := auth.GetAdminSession(ctx, options.Options.Region, "") + params := map[string]string{ + "scope": "system", + "cloudaccount_id": account.Id, + "status": cloudid.SAML_PROVIDER_STATUS_AVAILABLE, + } + samlproviders, _ := modules.SAMLProviders.List(s, jsonutils.Marshal(params)) + for _, sp := range samlproviders.Data { + authUrl, _ := sp.GetString("auth_url") + if len(authUrl) > 0 { + output.InitLoginUrl = authUrl + break + } } - return output, nil } diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index 97345d7856..ff5b50b257 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -163,6 +163,8 @@ type SCloudaccount struct { // 公有云子账号登录地址 IamLoginUrl string `width:"512" charset:"ascii" nullable:"false" list:"domain" update:"domain"` + + SAMLAuth tristate.TriState `nullable:"false" get:"user" update:"domain" create:"optional" list:"user" default:"false"` } func (self *SCloudaccount) GetCloudproviders() []SCloudprovider { @@ -993,6 +995,9 @@ func (manager *SCloudaccountManager) validateCreateData( if err != nil { return input, err } + if input.SAMLAuth != nil && *input.SAMLAuth && !providerDriver.IsSupportSAMLAuth() { + return input, httperrors.NewNotSupportedError("%s not support saml auth", input.Provider) + } if len(input.Brand) > 0 && input.Brand != providerDriver.GetName() { brands := providerDriver.GetSupportedBrands() if !utils.IsInStringArray(providerDriver.GetName(), brands) { diff --git a/pkg/cloudid/saml/providers/register.go b/pkg/mcclient/modules/mod_cloudrole.go similarity index 61% rename from pkg/cloudid/saml/providers/register.go rename to pkg/mcclient/modules/mod_cloudrole.go index 4ecbf79ef4..30ac69b807 100644 --- a/pkg/cloudid/saml/providers/register.go +++ b/pkg/mcclient/modules/mod_cloudrole.go @@ -12,23 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -package providers +package modules -var ( - driverTable = make(map[string]ICloudSAMLLoginDriver) +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" ) -func Register(driver ICloudSAMLLoginDriver) { - driverTable[driver.GetEntityID()] = driver +type SCloudroleManager struct { + modulebase.ResourceManager } -func FindDriver(entityId string) ICloudSAMLLoginDriver { - if driver, ok := driverTable[entityId]; ok { - return driver - } - return nil -} +var ( + Cloudroles SCloudroleManager +) -func AllDrivers() map[string]ICloudSAMLLoginDriver { - return driverTable +func init() { + Cloudroles = SCloudroleManager{NewCloudIdManager("cloudrole", "cloudroles", + []string{}, + []string{})} + + register(&Cloudroles) } diff --git a/pkg/cloudid/saml/providers/types.go b/pkg/mcclient/modules/mod_saml_provider.go similarity index 55% rename from pkg/cloudid/saml/providers/types.go rename to pkg/mcclient/modules/mod_saml_provider.go index a7b9b4c29a..092bd52330 100644 --- a/pkg/cloudid/saml/providers/types.go +++ b/pkg/mcclient/modules/mod_saml_provider.go @@ -12,19 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -package providers +package modules import ( - "yunion.io/x/onecloud/pkg/util/samlutils" - "yunion.io/x/onecloud/pkg/util/samlutils/idp" + "yunion.io/x/onecloud/pkg/mcclient/modulebase" ) -type ICloudSAMLLoginDriver interface { - GetEntityID() string - - GetMetadataFilename() string - GetMetadataUrl() string - - GetIdpInitiatedLoginData(idpId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) - GetSpInitiatedLoginData(idpId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) +type SSAMLProviderManager struct { + modulebase.ResourceManager +} + +var ( + SAMLProviders SSAMLProviderManager +) + +func init() { + SAMLProviders = SSAMLProviderManager{NewCloudIdManager("saml_provider", "saml_providers", + []string{}, + []string{})} + + register(&SAMLProviders) } diff --git a/pkg/mcclient/modules/mod_samluser.go b/pkg/mcclient/modules/mod_samluser.go new file mode 100644 index 0000000000..88124c07ae --- /dev/null +++ b/pkg/mcclient/modules/mod_samluser.go @@ -0,0 +1,35 @@ +// 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 modules + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" +) + +type SSamluserManager struct { + modulebase.ResourceManager +} + +var ( + Samlusers SClouduserManager +) + +func init() { + Samlusers = SClouduserManager{NewCloudIdManager("samluser", "samlusers", + []string{}, + []string{})} + + register(&Samlusers) +} diff --git a/pkg/mcclient/options/cloudaccounts.go b/pkg/mcclient/options/cloudaccounts.go index be2e752825..7b3938a904 100644 --- a/pkg/mcclient/options/cloudaccounts.go +++ b/pkg/mcclient/options/cloudaccounts.go @@ -464,6 +464,7 @@ type SCloudAccountUpdateBaseOptions struct { SyncIntervalSeconds *int `help:"auto synchornize interval in seconds"` AutoCreateProject *bool `help:"automatically create local project for new remote project"` ProxySetting string `help:"proxy setting name or id" json:"proxy_setting"` + SamlAuth string `help:"Enable or disable saml auth" choices:"true|false"` Desc string `help:"Description" json:"description" token:"desc"` } diff --git a/pkg/mcclient/options/cloudgroup.go b/pkg/mcclient/options/cloudid/cloudgroup.go similarity index 94% rename from pkg/mcclient/options/cloudgroup.go rename to pkg/mcclient/options/cloudid/cloudgroup.go index 9ee725ca64..ecef8754ed 100644 --- a/pkg/mcclient/options/cloudgroup.go +++ b/pkg/mcclient/options/cloudid/cloudgroup.go @@ -12,12 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package options +package cloudid -import "yunion.io/x/jsonutils" +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) type CloudgroupListOptions struct { - BaseListOptions + options.BaseListOptions ClouduserId string `json:"clouduser_id"` CloudpolicyId string `json:"cloudpolicy_id"` @@ -25,7 +29,7 @@ type CloudgroupListOptions struct { } func (opts *CloudgroupListOptions) Params() (jsonutils.JSONObject, error) { - return ListStructToParams(opts) + return options.ListStructToParams(opts) } type CloudgroupCreateOptions struct { diff --git a/pkg/mcclient/options/cloudid/cloudgroupcache.go b/pkg/mcclient/options/cloudid/cloudgroupcache.go new file mode 100644 index 0000000000..bdc8d7d0e7 --- /dev/null +++ b/pkg/mcclient/options/cloudid/cloudgroupcache.go @@ -0,0 +1,43 @@ +// 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 cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type CloudgroupcacheListOptions struct { + options.BaseListOptions + + CloudgroupId string `json:"cloudgroup_id"` +} + +func (opts *CloudgroupcacheListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +type CloudgroupcacheIdOption struct { + ID string `help:"Cloudgroup cache Id"` +} + +func (opts *CloudgroupcacheIdOption) GetId() string { + return opts.ID +} + +func (opts *CloudgroupcacheIdOption) Params() (jsonutils.JSONObject, error) { + return nil, nil +} diff --git a/pkg/mcclient/options/cloudid/cloudgrouppolicy.go b/pkg/mcclient/options/cloudid/cloudgrouppolicy.go new file mode 100644 index 0000000000..1ccc5c56b9 --- /dev/null +++ b/pkg/mcclient/options/cloudid/cloudgrouppolicy.go @@ -0,0 +1,35 @@ +// 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 cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type CloudgroupPolicyListOptions struct { + options.BaseListOptions + Cloudgroup string `help:"ID or Name of Cloudgroup"` + Cloudpolicy string `help:"Policy ID or name"` +} + +func (opts *CloudgroupPolicyListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +func (opts *CloudgroupPolicyListOptions) GetContextId() string { + return opts.Cloudgroup +} diff --git a/pkg/mcclient/options/cloudid/cloudgroupuser.go b/pkg/mcclient/options/cloudid/cloudgroupuser.go new file mode 100644 index 0000000000..c4f210c368 --- /dev/null +++ b/pkg/mcclient/options/cloudid/cloudgroupuser.go @@ -0,0 +1,35 @@ +// 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 cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type CloudgroupUserListOptions struct { + options.BaseListOptions + Cloudgroup string `help:"ID or Name of Cloudgroup"` + Clouduser string `help:"User ID or name"` +} + +func (opts *CloudgroupUserListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +func (opts *CloudgroupUserListOptions) GetContextId() string { + return opts.Clouduser +} diff --git a/pkg/mcclient/options/cloudpolicy.go b/pkg/mcclient/options/cloudid/cloudpolicy.go similarity index 95% rename from pkg/mcclient/options/cloudpolicy.go rename to pkg/mcclient/options/cloudid/cloudpolicy.go index 77771b05f8..935d698686 100644 --- a/pkg/mcclient/options/cloudpolicy.go +++ b/pkg/mcclient/options/cloudid/cloudpolicy.go @@ -12,15 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -package options +package cloudid import ( "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/mcclient/options" ) type CloudpolicyListOptions struct { - BaseListOptions + options.BaseListOptions CloudproviderId string `json:"cloudprovider_id"` ClouduserId string `json:"clouduser_id"` @@ -29,7 +31,7 @@ type CloudpolicyListOptions struct { } func (opts *CloudpolicyListOptions) Params() (jsonutils.JSONObject, error) { - return ListStructToParams(opts) + return options.ListStructToParams(opts) } type CloudpolicyIdOptions struct { diff --git a/pkg/mcclient/options/cloudid/cloudpolicycache.go b/pkg/mcclient/options/cloudid/cloudpolicycache.go new file mode 100644 index 0000000000..d740622461 --- /dev/null +++ b/pkg/mcclient/options/cloudid/cloudpolicycache.go @@ -0,0 +1,32 @@ +// 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 cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type CloudpolicycacheListOptions struct { + options.BaseListOptions + + CloudpolyId string + CloudaccountId string +} + +func (opts *CloudpolicycacheListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} diff --git a/pkg/mcclient/options/cloudid/cloudrole.go b/pkg/mcclient/options/cloudid/cloudrole.go new file mode 100644 index 0000000000..7b08c8a62d --- /dev/null +++ b/pkg/mcclient/options/cloudid/cloudrole.go @@ -0,0 +1,41 @@ +// 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 cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type CloudroleListOptions struct { + options.BaseListOptions +} + +func (opts *CloudroleListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +type CloudroleIdOptions struct { + ID string `help:"Cloudrole Id"` +} + +func (opts *CloudroleIdOptions) GetId() string { + return opts.ID +} + +func (opts *CloudroleIdOptions) Params() (jsonutils.JSONObject, error) { + return nil, nil +} diff --git a/pkg/mcclient/options/clouduser.go b/pkg/mcclient/options/cloudid/clouduser.go similarity index 95% rename from pkg/mcclient/options/clouduser.go rename to pkg/mcclient/options/cloudid/clouduser.go index 88041d6e27..b9bd2da7ec 100644 --- a/pkg/mcclient/options/clouduser.go +++ b/pkg/mcclient/options/cloudid/clouduser.go @@ -12,12 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package options +package cloudid -import "yunion.io/x/jsonutils" +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) type ClouduserListOptions struct { - BaseListOptions + options.BaseListOptions CloudaccountId string `help:"Cloudaccount Id"` CloudproviderId string `help:"Cloudprovider Id"` CloudpolicyId string `help:"filter cloudusers by cloudpolicy"` @@ -25,7 +29,7 @@ type ClouduserListOptions struct { } func (opts *ClouduserListOptions) Params() (jsonutils.JSONObject, error) { - return ListStructToParams(opts) + return options.ListStructToParams(opts) } type ClouduserCreateOptions struct { diff --git a/pkg/mcclient/options/cloudid/clouduserpolicy.go b/pkg/mcclient/options/cloudid/clouduserpolicy.go new file mode 100644 index 0000000000..80259a7430 --- /dev/null +++ b/pkg/mcclient/options/cloudid/clouduserpolicy.go @@ -0,0 +1,35 @@ +// 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 cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type ClouduserPolicyListOptions struct { + options.BaseListOptions + Clouduser string `help:"ID or Name of Clouduser"` + Cloudpolicy string `help:"Policy ID or name"` +} + +func (opts *ClouduserPolicyListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +func (opts *ClouduserPolicyListOptions) GetContextId() string { + return opts.Cloudpolicy +} diff --git a/pkg/mcclient/options/cloudid/doc.go b/pkg/mcclient/options/cloudid/doc.go new file mode 100644 index 0000000000..d19cee6424 --- /dev/null +++ b/pkg/mcclient/options/cloudid/doc.go @@ -0,0 +1 @@ +package cloudid // import "yunion.io/x/onecloud/pkg/mcclient/options/cloudid" diff --git a/pkg/mcclient/options/cloudid/saml_provider.go b/pkg/mcclient/options/cloudid/saml_provider.go new file mode 100644 index 0000000000..92a5e81222 --- /dev/null +++ b/pkg/mcclient/options/cloudid/saml_provider.go @@ -0,0 +1,49 @@ +// 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 cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type SAMLProviderListOptions struct { + options.BaseListOptions +} + +func (opts *SAMLProviderListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +type SAMLProviderIdOptions struct { + ID string `help:"SAML Provider Id"` +} + +func (opts *SAMLProviderIdOptions) GetId() string { + return opts.ID +} + +func (opts *SAMLProviderIdOptions) Params() (jsonutils.JSONObject, error) { + return nil, nil +} + +type SAMLProviderCreateOptions struct { + CLOUDACCOUNT_ID string +} + +func (opts *SAMLProviderCreateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts), nil +} diff --git a/pkg/mcclient/options/cloudid/samluser.go b/pkg/mcclient/options/cloudid/samluser.go new file mode 100644 index 0000000000..373e82d6d1 --- /dev/null +++ b/pkg/mcclient/options/cloudid/samluser.go @@ -0,0 +1,53 @@ +// 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 cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type SamluserListOptions struct { + options.BaseListOptions + CloudgroupId string `help:"Filter by cloudgroup"` + CloudaccountId string `help:"Filter by cloudaccount"` +} + +func (opts *SamluserListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +type SamluserCreateOptions struct { + OwnerId string + CLOUDGROUP_ID string `help:"CloudgroupId"` + CLOUDACCOUNT_ID string `help:"CloudaccountId"` +} + +func (opts *SamluserCreateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts), nil +} + +type SamluserIdOptions struct { + ID string `help:"SAML user Id"` +} + +func (opts *SamluserIdOptions) GetId() string { + return opts.ID +} + +func (opts *SamluserIdOptions) Params() (jsonutils.JSONObject, error) { + return nil, nil +} diff --git a/pkg/multicloud/aliyun/provider/provider.go b/pkg/multicloud/aliyun/provider/provider.go index 18dc2955ef..2be3948d5c 100644 --- a/pkg/multicloud/aliyun/provider/provider.go +++ b/pkg/multicloud/aliyun/provider/provider.go @@ -329,10 +329,6 @@ func (self *SAliyunProvider) GetSamlEntityId() string { return cloudprovider.SAML_ENTITY_ID_ALIYUN_ROLE } -func (self *SAliyunProvider) GetSamlSpInitiatedLoginUrl(idpName string) string { - return "" -} - func (self *SAliyunProvider) GetICloudDnsZones() ([]cloudprovider.ICloudDnsZone, error) { izones := []cloudprovider.ICloudDnsZone{} privateZone, err := self.client.GetPrivateICloudDnsZones() diff --git a/pkg/multicloud/aliyun/saml_provider.go b/pkg/multicloud/aliyun/saml_provider.go new file mode 100644 index 0000000000..7d33b4d491 --- /dev/null +++ b/pkg/multicloud/aliyun/saml_provider.go @@ -0,0 +1,37 @@ +// 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 aliyun + +import ( + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SAMLProvider struct { + multicloud.SResourceBase + client *SAliyunClient +} + +func (self *SAliyunClient) ListSAMLProviders() ([]SAMLProvider, error) { + result := []SAMLProvider{} + resp, err := self.ramRequest("ListSAMLProviders", map[string]string{}) + if err != nil { + return nil, errors.Wrapf(err, "ListSAMLProviders") + } + log.Errorf("result: %s", resp.PrettyString()) + return result, nil +} diff --git a/pkg/multicloud/aliyun/shell/saml_provider.go b/pkg/multicloud/aliyun/shell/saml_provider.go new file mode 100644 index 0000000000..d82351c2dc --- /dev/null +++ b/pkg/multicloud/aliyun/shell/saml_provider.go @@ -0,0 +1,36 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SamlProviderListOptions struct { + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&SamlProviderListOptions{}, "saml-provider-list", "List saml provider", func(cli *aliyun.SRegion, args *SamlProviderListOptions) error { + result, err := cli.GetClient().ListSAMLProviders() + if err != nil { + return err + } + printList(result, 0, 0, 0, []string{}) + return nil + }) + +} diff --git a/pkg/multicloud/aws/iam_role.go b/pkg/multicloud/aws/iam_role.go index f623dbde79..081aed0dce 100644 --- a/pkg/multicloud/aws/iam_role.go +++ b/pkg/multicloud/aws/iam_role.go @@ -16,9 +16,18 @@ package aws import ( "fmt" + "net/url" "time" + "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +var ( + samlRole = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"sts:AssumeRoleWithSAML","Principal":{"Federated":"%s"},"Condition":{"StringEquals":{"SAML:aud":["%s"]}}}]}` ) type SRole struct { @@ -34,6 +43,78 @@ type SRole struct { CreateDate time.Time `xml:"CreateDate"` } +func (self *SRole) GetGlobalId() string { + return self.Arn +} + +func (self *SRole) GetName() string { + return self.RoleName +} + +func (self *SRole) Delete() error { + return self.client.DeleteRole(self.RoleName) +} + +func (self *SRole) GetDocument() *jsonutils.JSONDict { + data, err := url.QueryUnescape(self.AssumeRolePolicyDocument) + if err != nil { + return nil + } + document, err := jsonutils.Parse([]byte(data)) + if err != nil { + return nil + } + return document.(*jsonutils.JSONDict) +} + +//[{"Action":"sts:AssumeRoleWithSAML","Condition":{"StringEquals":{"SAML:aud":"https://signin.aws.amazon.com/saml"}},"Effect":"Allow","Principal":{"Federated":"arn:aws:iam::879324515906:saml-provider/quxuan"}}] +func (self *SRole) GetSAMLProvider() string { + document := self.GetDocument() + if document != nil { + statement, err := document.GetArray("Statement") + if err == nil { + for i := range statement { + if action, _ := statement[i].GetString("Action"); action == "sts:AssumeRoleWithSAML" { + sp, _ := statement[i].GetString("Principal", "Federated") + if len(sp) > 0 { + return sp + } + } + } + } + } + return "" +} + +func (self *SRole) AttachPolicy(id string) error { + return self.client.AttachRolePolicy(self.RoleName, self.client.getIamArn(id)) +} + +func (self *SRole) DetachPolicy(id string) error { + return self.client.DetachRolePolicy(self.RoleName, self.client.getIamArn(id)) +} + +func (self *SRole) GetICloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies := []SAttachedPolicy{} + marker := "" + for { + part, err := self.client.ListAttachedRolePolicies(self.RoleName, marker, 100, "") + if err != nil { + return nil, errors.Wrapf(err, "ListAttachedRolePolicies") + } + policies = append(policies, part.AttachedPolicies...) + marker = part.Marker + if len(marker) == 0 { + break + } + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + ret = append(ret, &policies[i]) + } + return ret, nil +} + type SRoles struct { Roles []SRole `xml:"Roles>member"` IsTruncated bool `xml:"IsTruncated"` @@ -81,3 +162,48 @@ func (self *SAwsClient) DeleteRole(name string) error { } return self.iamRequest("DeleteRole", params, nil) } + +func (self *SAwsClient) GetICloudroles() ([]cloudprovider.ICloudrole, error) { + roles := []SRole{} + marker := "" + for { + part, err := self.ListRoles(marker, 100, "") + if err != nil { + return nil, errors.Wrapf(err, "ListRoles") + } + roles = append(roles, part.Roles...) + marker = part.Marker + if len(marker) == 0 { + break + } + } + ret := []cloudprovider.ICloudrole{} + for i := range roles { + ret = append(ret, &roles[i]) + } + return ret, nil +} + +func (self *SAwsClient) CreateRole(opts *cloudprovider.SRoleCreateOptions) (*SRole, error) { + if len(opts.SAMLProvider) > 0 { + aud := "https://signin.amazonaws.cn/saml" + if self.GetAccessEnv() == api.CLOUD_ACCESS_ENV_AWS_GLOBAL { + aud = "https://signin.aws.amazon.com/saml" + } + params := map[string]string{ + "RoleName": opts.Name, + "Description": opts.Desc, + "AssumeRolePolicyDocument": fmt.Sprintf(samlRole, opts.SAMLProvider, aud), + } + role := struct { + Role SRole + }{} + err := self.iamRequest("CreateRole", params, &role) + if err != nil { + return nil, errors.Wrapf(err, "CreateRole") + } + role.Role.client = self + return &role.Role, nil + } + return nil, cloudprovider.ErrNotImplemented +} diff --git a/pkg/multicloud/aws/provider/provider.go b/pkg/multicloud/aws/provider/provider.go index 22eab27490..9bf98003b3 100644 --- a/pkg/multicloud/aws/provider/provider.go +++ b/pkg/multicloud/aws/provider/provider.go @@ -312,16 +312,59 @@ func (self *SAwsProvider) GetSamlEntityId() string { return self.client.GetSamlEntityId() } -func (self *SAwsProvider) GetSamlSpInitiatedLoginUrl(idpName string) string { - return "" -} - func (self *SAwsProvider) GetICloudDnsZones() ([]cloudprovider.ICloudDnsZone, error) { return self.client.GetICloudDnsZones() } + func (self *SAwsProvider) GetICloudDnsZoneById(id string) (cloudprovider.ICloudDnsZone, error) { return self.client.GetHostedZoneById(id) } + func (self *SAwsProvider) CreateICloudDnsZone(opts *cloudprovider.SDnsZoneCreateOptions) (cloudprovider.ICloudDnsZone, error) { return self.client.CreateHostedZone(opts) } + +func (self *SAwsProvider) GetICloudSAMLProviders() ([]cloudprovider.ICloudSAMLProvider, error) { + return self.client.GetICloudSAMLProviders() +} + +func (self *SAwsProvider) CreateICloudSAMLProvider(opts *cloudprovider.SAMLProviderCreateOptions) (cloudprovider.ICloudSAMLProvider, error) { + sp, err := self.client.CreateSAMLProvider(opts.Name, opts.Metadata.String()) + if err != nil { + return nil, errors.Wrap(err, "CreateSAMLProvider") + } + return sp, nil +} + +func (self *SAwsProvider) GetICloudroles() ([]cloudprovider.ICloudrole, error) { + return self.client.GetICloudroles() +} + +func (self *SAwsProvider) GetICloudroleById(id string) (cloudprovider.ICloudrole, error) { + roles, err := self.GetICloudroles() + if err != nil { + return nil, errors.Wrapf(err, "GetICloudroles") + } + for i := range roles { + if roles[i].GetGlobalId() == id { + return roles[i], nil + } + } + return nil, errors.Wrapf(cloudprovider.ErrNotFound, id) +} + +func (self *SAwsProvider) GetICloudroleByName(name string) (cloudprovider.ICloudrole, error) { + role, err := self.client.GetRole(name) + if err != nil { + return nil, errors.Wrapf(err, "GetRole(%s)", name) + } + return role, nil +} + +func (self *SAwsProvider) CreateICloudrole(opts *cloudprovider.SRoleCreateOptions) (cloudprovider.ICloudrole, error) { + role, err := self.client.CreateRole(opts) + if err != nil { + return nil, errors.Wrapf(err, "CreateRole") + } + return role, nil +} diff --git a/pkg/multicloud/aws/saml_provider.go b/pkg/multicloud/aws/saml_provider.go new file mode 100644 index 0000000000..64138a5a1e --- /dev/null +++ b/pkg/multicloud/aws/saml_provider.go @@ -0,0 +1,173 @@ +// 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 aws + +import ( + "fmt" + "strings" + "time" + "unicode" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/apis/cloudid" + api "yunion.io/x/onecloud/pkg/apis/cloudid" + compute_api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudid/options" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/util/httputils" + "yunion.io/x/onecloud/pkg/util/samlutils" +) + +type SAMLProvider struct { + multicloud.SResourceBase + client *SAwsClient + + SAMLMetadataDocument string `xml:"SAMLMetadataDocument"` + Arn string `xml:"Arn"` + ValidUntil time.Time `xml:"ValidUntil"` + CreateDate time.Time `xml:"CreateDate"` +} + +func (self *SAMLProvider) GetGlobalId() string { + return self.Arn +} + +func (self *SAMLProvider) GetId() string { + return self.Arn +} + +func (self *SAMLProvider) GetName() string { + if info := strings.Split(self.Arn, "/"); len(info) > 0 { + return info[len(info)-1] + } + return self.Arn +} + +func (self *SAMLProvider) GetStatus() string { + return api.SAML_PROVIDER_STATUS_AVAILABLE +} + +func (self *SAMLProvider) Delete() error { + return self.client.DeleteSAMLProvider(self.Arn) +} + +func (self *SAMLProvider) GetAuthUrl() string { + input := samlutils.SIdpInitiatedLoginInput{ + EntityID: cloudprovider.SAML_ENTITY_ID_AWS_CN, + IdpId: self.client.cpcfg.AccountId, + } + if self.client.GetAccessEnv() == compute_api.CLOUD_ACCESS_ENV_AWS_GLOBAL { + input.EntityID = cloudprovider.SAML_ENTITY_ID_AWS + } + return httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, fmt.Sprintf("sso?%s", jsonutils.Marshal(input).QueryString())) +} + +func (self *SAMLProvider) GetMetadataDocument() (*samlutils.EntityDescriptor, error) { + saml, err := self.client.GetSAMLProvider(self.Arn) + if err != nil { + return nil, errors.Wrapf(err, "GetSAMLProvider(%s)", self.Arn) + } + metadata, err := samlutils.ParseMetadata([]byte(saml.SAMLMetadataDocument)) + if err != nil { + return nil, errors.Wrapf(err, "ParseMetadata") + } + return &metadata, nil +} + +type SAMLProviders struct { + SAMLProviderList []SAMLProvider `xml:"SAMLProviderList>member"` +} + +func (self *SAwsClient) ListSAMLProviders() ([]SAMLProvider, error) { + result := SAMLProviders{} + err := self.iamRequest("ListSAMLProviders", nil, &result) + if err != nil { + return nil, errors.Wrapf(err, "ListSAMLProviders") + } + return result.SAMLProviderList, nil +} + +func (self *SAwsClient) GetSAMLProvider(arn string) (*SAMLProvider, error) { + result := &SAMLProvider{client: self, Arn: arn} + params := map[string]string{"SAMLProviderArn": arn} + err := self.iamRequest("GetSAMLProvider", params, result) + if err != nil { + return nil, errors.Wrapf(err, "GetSAMLProvider") + } + return result, nil +} + +func (self *SAwsClient) DeleteSAMLProvider(arn string) error { + params := map[string]string{"SAMLProviderArn": arn} + return self.iamRequest("DeleteSAMLProvider", params, nil) +} + +func (self *SAwsClient) CreateSAMLProvider(name, metadata string) (*SAMLProvider, error) { + name = func() string { + ret := "" + for _, s := range name { + if unicode.IsLetter(s) || unicode.IsNumber(s) || s == '.' || s == '_' || s == '-' { + ret += string(s) + } else { + ret += "-" + } + } + if len(ret) > 128 { + ret = ret[:128] + } + return ret + }() + params := map[string]string{ + "Name": name, + "SAMLMetadataDocument": metadata, + } + result := struct { + SAMLProviderArn string `xml:"SAMLProviderArn"` + }{} + err := self.iamRequest("CreateSAMLProvider", params, &result) + if err != nil { + return nil, errors.Wrapf(err, "CreateSAMLProvider") + } + return self.GetSAMLProvider(result.SAMLProviderArn) +} + +func (self *SAwsClient) UpdateSAMLProvider(arn, metadata string) (*SAMLProvider, error) { + params := map[string]string{ + "SAMLProviderArn": arn, + "SAMLMetadataDocument": metadata, + } + saml := &SAMLProvider{client: self} + err := self.iamRequest("UpdateSAMLProvider", params, saml) + if err != nil { + return nil, errors.Wrapf(err, "UpdateSAMLProvider") + } + return saml, nil +} + +func (self *SAwsClient) GetICloudSAMLProviders() ([]cloudprovider.ICloudSAMLProvider, error) { + providers, err := self.ListSAMLProviders() + if err != nil { + return nil, errors.Wrapf(err, "ListSAMLProviders") + } + ret := []cloudprovider.ICloudSAMLProvider{} + for i := range providers { + providers[i].client = self + ret = append(ret, &providers[i]) + } + return ret, nil +} diff --git a/pkg/multicloud/aws/shell/iam_role.go b/pkg/multicloud/aws/shell/iam_role.go index 7eca73e9ae..a61d82f6b3 100644 --- a/pkg/multicloud/aws/shell/iam_role.go +++ b/pkg/multicloud/aws/shell/iam_role.go @@ -15,6 +15,7 @@ package shell import ( + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/multicloud/aws" "yunion.io/x/onecloud/pkg/util/shellutils" ) @@ -44,6 +45,10 @@ func init() { return err } printObject(role) + document := role.GetDocument() + if document != nil { + printObject(document) + } return nil }) @@ -51,4 +56,13 @@ func init() { return cli.GetClient().DeleteRole(args.ROLE) }) + shellutils.R(&cloudprovider.SRoleCreateOptions{}, "cloud-role-create", "Create role", func(cli *aws.SRegion, args *cloudprovider.SRoleCreateOptions) error { + role, err := cli.GetClient().CreateRole(args) + if err != nil { + return err + } + printObject(role) + return nil + }) + } diff --git a/pkg/multicloud/aws/shell/saml_provider.go b/pkg/multicloud/aws/shell/saml_provider.go new file mode 100644 index 0000000000..544624e879 --- /dev/null +++ b/pkg/multicloud/aws/shell/saml_provider.go @@ -0,0 +1,79 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/aws" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SAMLProviderListOptions struct { + } + shellutils.R(&SAMLProviderListOptions{}, "saml-provider-list", "List saml providers", func(cli *aws.SRegion, args *SAMLProviderListOptions) error { + samls, err := cli.GetClient().ListSAMLProviders() + if err != nil { + return err + } + printList(samls, 0, 0, 0, []string{}) + return nil + }) + + type SAMLProviderArnOptions struct { + ARN string + } + + shellutils.R(&SAMLProviderArnOptions{}, "saml-provider-show", "Show saml provider", func(cli *aws.SRegion, args *SAMLProviderArnOptions) error { + saml, err := cli.GetClient().GetSAMLProvider(args.ARN) + if err != nil { + return err + } + printObject(saml) + return nil + }) + + shellutils.R(&SAMLProviderArnOptions{}, "saml-provider-delete", "Delete saml provider", func(cli *aws.SRegion, args *SAMLProviderArnOptions) error { + return cli.GetClient().DeleteSAMLProvider(args.ARN) + }) + + type SAMLProviderCreateOptions struct { + NAME string + METADATA string + } + + shellutils.R(&SAMLProviderCreateOptions{}, "saml-provider-create", "Create saml provider", func(cli *aws.SRegion, args *SAMLProviderCreateOptions) error { + saml, err := cli.GetClient().CreateSAMLProvider(args.NAME, args.METADATA) + if err != nil { + return err + } + printObject(saml) + return nil + }) + + type SAMLProviderUpdateOptions struct { + ARN string + METADATA string + } + + shellutils.R(&SAMLProviderUpdateOptions{}, "saml-provider-update", "Update saml provider", func(cli *aws.SRegion, args *SAMLProviderUpdateOptions) error { + saml, err := cli.GetClient().UpdateSAMLProvider(args.ARN, args.METADATA) + if err != nil { + return err + } + printObject(saml) + return nil + }) + +} diff --git a/pkg/multicloud/google/provider/provider.go b/pkg/multicloud/google/provider/provider.go index 2d4b4ccd07..57631e2947 100644 --- a/pkg/multicloud/google/provider/provider.go +++ b/pkg/multicloud/google/provider/provider.go @@ -297,7 +297,3 @@ func (self *SGoogleProvider) CreateICloudpolicy(opts *cloudprovider.SCloudpolicy func (self *SGoogleProvider) GetSamlEntityId() string { return cloudprovider.SAML_ENTITY_ID_GOOGLE } - -func (self *SGoogleProvider) GetSamlSpInitiatedLoginUrl(idpName string) string { - return self.client.GetSamlSpInitiatedLoginUrl(idpName) -} diff --git a/pkg/multicloud/huawei/client/client.go b/pkg/multicloud/huawei/client/client.go index 00cc286051..6ceec2a085 100644 --- a/pkg/multicloud/huawei/client/client.go +++ b/pkg/multicloud/huawei/client/client.go @@ -33,60 +33,62 @@ type Client struct { // 标记初始化状态 init bool - Balances *modules.SBalanceManager - Bandwidths *modules.SBandwidthManager - Credentials *modules.SCredentialManager - Disks *modules.SDiskManager - Domains *modules.SDomainManager - Eips *modules.SEipManager - Elasticcache *modules.SElasticcacheManager - DcsAvailableZone *modules.SDcsAvailableZoneManager - Flavors *modules.SFlavorManager - Images *modules.SImageManager - OpenStackImages *modules.SImageManager - Interface *modules.SInterfaceManager - Jobs *modules.SJobManager - Keypairs *modules.SKeypairManager - Elb *modules.SLoadbalancerManager - ElbBackend *modules.SElbBackendManager - ElbBackendGroup *modules.SElbBackendGroupManager - ElbListeners *modules.SElbListenersManager - ElbCertificates *modules.SElbCertificatesManager - ElbHealthCheck *modules.SElbHealthCheckManager - ElbL7policies *modules.SElbL7policiesManager - ElbPolicies *modules.SElbPoliciesManager - ElbWhitelist *modules.SElbWhitelistManager - Orders *modules.SOrderManager - Port *modules.SPortManager - Projects *modules.SProjectManager - Regions *modules.SRegionManager - SecurityGroupRules *modules.SSecgroupRuleManager - SecurityGroups *modules.SSecurityGroupManager - NovaSecurityGroups *modules.SSecurityGroupManager - Servers *modules.SServerManager - ServersV2 *modules.SServerManager - NovaServers *modules.SServerManager - Snapshots *modules.SSnapshotManager - OsSnapshots *modules.SSnapshotManager - Subnets *modules.SSubnetManager - Users *modules.SUserManager - Vpcs *modules.SVpcManager - Zones *modules.SZoneManager - VpcRoutes *modules.SVpcRouteManager - SNatRules *modules.SNatSRuleManager - DNatRules *modules.SNatDRuleManager - NatGateways *modules.SNatGatewayManager - VpcPeerings *modules.SVpcPeeringManager - DBInstance *modules.SDBInstanceManager - DBInstanceBackup *modules.SDBInstanceBackupManager - DBInstanceFlavor *modules.SDBInstanceFlavorManager - DBInstanceJob *modules.SDBInstanceJobManager - Traces *modules.STraceManager - CloudEye *modules.SCloudEyeManager - Quotas *modules.SQuotaManager - EnterpriseProjects *modules.SEnterpriseProjectManager - Roles *modules.SRoleManager - Groups *modules.SGroupManager + Balances *modules.SBalanceManager + Bandwidths *modules.SBandwidthManager + Credentials *modules.SCredentialManager + Disks *modules.SDiskManager + Domains *modules.SDomainManager + Eips *modules.SEipManager + Elasticcache *modules.SElasticcacheManager + DcsAvailableZone *modules.SDcsAvailableZoneManager + Flavors *modules.SFlavorManager + Images *modules.SImageManager + OpenStackImages *modules.SImageManager + Interface *modules.SInterfaceManager + Jobs *modules.SJobManager + Keypairs *modules.SKeypairManager + Elb *modules.SLoadbalancerManager + ElbBackend *modules.SElbBackendManager + ElbBackendGroup *modules.SElbBackendGroupManager + ElbListeners *modules.SElbListenersManager + ElbCertificates *modules.SElbCertificatesManager + ElbHealthCheck *modules.SElbHealthCheckManager + ElbL7policies *modules.SElbL7policiesManager + ElbPolicies *modules.SElbPoliciesManager + ElbWhitelist *modules.SElbWhitelistManager + Orders *modules.SOrderManager + Port *modules.SPortManager + Projects *modules.SProjectManager + Regions *modules.SRegionManager + SecurityGroupRules *modules.SSecgroupRuleManager + SecurityGroups *modules.SSecurityGroupManager + NovaSecurityGroups *modules.SSecurityGroupManager + Servers *modules.SServerManager + ServersV2 *modules.SServerManager + NovaServers *modules.SServerManager + Snapshots *modules.SSnapshotManager + OsSnapshots *modules.SSnapshotManager + Subnets *modules.SSubnetManager + Users *modules.SUserManager + Vpcs *modules.SVpcManager + Zones *modules.SZoneManager + VpcRoutes *modules.SVpcRouteManager + SNatRules *modules.SNatSRuleManager + DNatRules *modules.SNatDRuleManager + NatGateways *modules.SNatGatewayManager + VpcPeerings *modules.SVpcPeeringManager + DBInstance *modules.SDBInstanceManager + DBInstanceBackup *modules.SDBInstanceBackupManager + DBInstanceFlavor *modules.SDBInstanceFlavorManager + DBInstanceJob *modules.SDBInstanceJobManager + Traces *modules.STraceManager + CloudEye *modules.SCloudEyeManager + Quotas *modules.SQuotaManager + EnterpriseProjects *modules.SEnterpriseProjectManager + Roles *modules.SRoleManager + Groups *modules.SGroupManager + SAMLProviders *modules.SAMLProviderManager + SAMLProviderMappings *modules.SAMLProviderMappingManager } func (self *Client) SetHttpClient(httpClient *http.Client) { @@ -142,6 +144,8 @@ func (self *Client) SetHttpClient(httpClient *http.Client) { self.EnterpriseProjects.SetHttpClient(httpClient) self.Roles.SetHttpClient(httpClient) self.Groups.SetHttpClient(httpClient) + self.SAMLProviders.SetHttpClient(httpClient) + self.SAMLProviderMappings.SetHttpClient(httpClient) } func (self *Client) InitWithOptions(regionId, domainId, projectId string, credential auth.Credential) error { @@ -229,6 +233,10 @@ func (self *Client) initManagers() { self.Roles.SetDomainId(self.domainId) self.Groups = modules.NewGroupManager(self.signer, self.debug) self.Groups.SetDomainId(self.domainId) + self.SAMLProviders = modules.NewSAMLProviderManager(self.signer, self.debug) + self.SAMLProviders.SetDomainId(self.domainId) + self.SAMLProviderMappings = modules.NewSAMLProviderMappingManager(self.signer, self.debug) + self.SAMLProviderMappings.SetDomainId(self.domainId) } self.init = true diff --git a/pkg/multicloud/huawei/client/modules/manager_resource.go b/pkg/multicloud/huawei/client/modules/manager_resource.go index 033c03f362..b769e0bb44 100644 --- a/pkg/multicloud/huawei/client/modules/manager_resource.go +++ b/pkg/multicloud/huawei/client/modules/manager_resource.go @@ -170,6 +170,10 @@ func (self *SResourceManager) GetInContextWithSpec(ctx manager.IManagerContext, request.AddQueryParam(k, v) } + if len(self.DomainId) > 0 { + request.AddHeaderParam("X-Domain-Id", self.DomainId) + } + return self._get(request, responseKey) } @@ -216,6 +220,27 @@ func (self *SResourceManager) UpdateInContextWithSpec(ctx manager.IManagerContex return self._do(request, responseKey) } +func (self *SResourceManager) Patch(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.PatchInContext(self.ctx, id, params) +} + +func (self *SResourceManager) PatchInContext(ctx manager.IManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.PatchInContextWithSpec(ctx, id, "", params, self.Keyword) +} + +func (self *SResourceManager) PatchInContextWithSpec(ctx manager.IManagerContext, id string, spec string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) { + request := self.newRequest("PATCH", id, spec, ctx) + content := getContent(params) + if len(content) > 0 { + request.SetContent([]byte(content)) + } + if len(self.DomainId) > 0 { + request.AddHeaderParam("X-Domain-Id", self.DomainId) + } + + return self._do(request, responseKey) +} + func (self *SResourceManager) Delete(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { return self.DeleteInContext(self.ctx, id, params) } @@ -248,6 +273,9 @@ func (self *SResourceManager) PerformAction(action string, id string, params jso func (self *SResourceManager) PerformAction2(action string, id string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) { request := self.newRequest("POST", id, action, nil) request.SetContent([]byte(getContent(params))) + if len(self.DomainId) > 0 { + request.AddHeaderParam("X-Domain-Id", self.DomainId) + } return self._do(request, responseKey) } diff --git a/pkg/multicloud/huawei/client/modules/mod_mapping.go b/pkg/multicloud/huawei/client/modules/mod_mapping.go new file mode 100644 index 0000000000..9aefbab469 --- /dev/null +++ b/pkg/multicloud/huawei/client/modules/mod_mapping.go @@ -0,0 +1,37 @@ +// 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 modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" +) + +type SAMLProviderMappingManager struct { + SResourceManager +} + +func NewSAMLProviderMappingManager(signer auth.Signer, debug bool) *SAMLProviderMappingManager { + return &SAMLProviderMappingManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(signer, debug), + ServiceName: ServiceNameIAM, + Region: "", + ProjectId: "", + version: "v3/OS-FEDERATION", + Keyword: "mapping", + KeywordPlural: "mappings", + + ResourceKeyword: "mappings", + }} +} diff --git a/pkg/multicloud/huawei/client/modules/mod_saml_provider.go b/pkg/multicloud/huawei/client/modules/mod_saml_provider.go new file mode 100644 index 0000000000..435a4cd4cb --- /dev/null +++ b/pkg/multicloud/huawei/client/modules/mod_saml_provider.go @@ -0,0 +1,37 @@ +// 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 modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" +) + +type SAMLProviderManager struct { + SResourceManager +} + +func NewSAMLProviderManager(signer auth.Signer, debug bool) *SAMLProviderManager { + return &SAMLProviderManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(signer, debug), + ServiceName: ServiceNameIAM, + Region: "", + ProjectId: "", + version: "v3/OS-FEDERATION", + Keyword: "identity_provider", + KeywordPlural: "identity_providers", + + ResourceKeyword: "identity_providers", + }} +} diff --git a/pkg/multicloud/huawei/huawei.go b/pkg/multicloud/huawei/huawei.go index 74453960c0..8a02a38a77 100644 --- a/pkg/multicloud/huawei/huawei.go +++ b/pkg/multicloud/huawei/huawei.go @@ -560,7 +560,3 @@ func (self *SHuaweiClient) initOwner() error { self.ownerId = ownerId return nil } - -func (self *SHuaweiClient) GetSamlSpInitiatedLoginUrl(idpName string) string { - return fmt.Sprintf("https://auth.huaweicloud.com/authui/federation/websso?domain_id=%s&idp=%s&protocol=saml", self.ownerId, idpName) -} diff --git a/pkg/multicloud/huawei/provider/provider.go b/pkg/multicloud/huawei/provider/provider.go index 683f2d34f9..ac7b108a07 100644 --- a/pkg/multicloud/huawei/provider/provider.go +++ b/pkg/multicloud/huawei/provider/provider.go @@ -289,6 +289,14 @@ func (self *SHuaweiProvider) GetSamlEntityId() string { return cloudprovider.SAML_ENTITY_ID_HUAWEI_CLOUD } -func (self *SHuaweiProvider) GetSamlSpInitiatedLoginUrl(idpName string) string { - return self.client.GetSamlSpInitiatedLoginUrl(idpName) +func (self *SHuaweiProvider) GetICloudSAMLProviders() ([]cloudprovider.ICloudSAMLProvider, error) { + return self.client.GetICloudSAMLProviders() +} + +func (self *SHuaweiProvider) CreateICloudSAMLProvider(opts *cloudprovider.SAMLProviderCreateOptions) (cloudprovider.ICloudSAMLProvider, error) { + sp, err := self.client.CreateSAMLProvider(opts) + if err != nil { + return nil, errors.Wrapf(err, "CreateSAMLProvider") + } + return sp, nil } diff --git a/pkg/multicloud/huawei/saml_provider.go b/pkg/multicloud/huawei/saml_provider.go new file mode 100644 index 0000000000..c051b98813 --- /dev/null +++ b/pkg/multicloud/huawei/saml_provider.go @@ -0,0 +1,321 @@ +// 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 huawei + +import ( + "fmt" + "time" + "unicode" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/modules" + "yunion.io/x/onecloud/pkg/util/samlutils" +) + +type SAMLProviderLinks struct { + Self string + Protocols string +} + +type SAMLProvider struct { + multicloud.SResourceBase + client *SHuaweiClient + + Id string + Links SAMLProviderLinks + Description string +} + +func (self *SAMLProvider) GetId() string { + return self.Id +} + +func (self *SAMLProvider) GetGlobalId() string { + return self.Id +} + +func (self *SAMLProvider) GetName() string { + return self.Id +} + +func (self *SAMLProvider) GetStatus() string { + mappings, err := self.client.ListSAMLProviderMappings() + if err != nil { + return api.SAML_PROVIDER_STATUS_UNKNOWN + } + for i := range mappings { + if mappings[i].Id == DEFAULT_ONECLOUD_MAPPING { + return api.SAML_PROVIDER_STATUS_AVAILABLE + } + } + return api.SAML_PROVIDER_STATUS_UNVALIABLE +} + +func (self *SAMLProvider) GetAuthUrl() string { + return fmt.Sprintf("https://auth.huaweicloud.com/authui/federation/websso?domain_id=%s&idp=%s&protocol=saml", self.client.ownerId, self.Id) +} + +func (self *SAMLProvider) Delete() error { + return self.client.DeleteSAMLProvider(self.Id) +} + +func (self *SAMLProvider) GetMetadataDocument() (*samlutils.EntityDescriptor, error) { + info, err := self.client.GetSAMLProviderMetadata(self.Id) + if err != nil { + return nil, errors.Wrapf(err, "GetSAMLProviderMetadata(%s)", self.Id) + } + metadata, err := samlutils.ParseMetadata([]byte(info.Data)) + if err != nil { + return nil, errors.Wrapf(err, "ParseMetadata") + } + return &metadata, nil +} + +func (self *SHuaweiClient) ListSAMLProviders() ([]SAMLProvider, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrapf(err, "newGeneralAPIClient") + } + samls := []SAMLProvider{} + err = doListAllWithNextLink(client.SAMLProviders.List, nil, &samls) + if err != nil { + return nil, errors.Wrapf(err, "doListAll") + } + return samls, nil +} + +type SAMLProviderProtocol struct { + MappingId string + Id string +} + +func (self *SHuaweiClient) GetSAMLProviderProtocols(id string) ([]SAMLProviderProtocol, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + resp, err := client.SAMLProviders.ListInContextWithSpec(nil, fmt.Sprintf("%s/protocols", id), nil, "protocols") + if err != nil { + return nil, errors.Wrapf(err, "ListInContextWithSpec") + } + protocols := []SAMLProviderProtocol{} + return protocols, jsonutils.Update(&protocols, resp.Data) +} + +type SAMLProviderMetadata struct { + DomainId string + UpdateTime time.Time + Data string + IdpId string + ProtocolId string + Id string + EntityId string + XaccountType string +} + +func (self *SHuaweiClient) GetSAMLProviderMetadata(id string) (*SAMLProviderMetadata, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + client.SAMLProviders.SetVersion("v3-ext/OS-FEDERATION") + resp, err := client.SAMLProviders.GetInContextWithSpec(nil, id, fmt.Sprintf("protocols/saml/metadata"), nil, "") + if err != nil { + return nil, err + } + + metadata := &SAMLProviderMetadata{} + err = resp.Unmarshal(metadata) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return metadata, nil +} + +func (self *SHuaweiClient) UpdateSAMLProviderMetadata(id, metadata string) error { + params := map[string]string{ + "domain_id": self.ownerId, + "xaccount_type": "", + "metadata": metadata, + } + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + client.SAMLProviders.SetVersion("v3-ext/OS-FEDERATION") + _, err = client.SAMLProviders.PerformAction2("protocols/saml/metadata", id, jsonutils.Marshal(params), "") + if err != nil { + return errors.Wrapf(err, "SAMLProvider.PerformAction") + } + return nil +} + +func (self *SHuaweiClient) GetICloudSAMLProviders() ([]cloudprovider.ICloudSAMLProvider, error) { + samls, err := self.ListSAMLProviders() + if err != nil { + return nil, errors.Wrapf(err, "ListSAMLProviders") + } + ret := []cloudprovider.ICloudSAMLProvider{} + for i := range samls { + samls[i].client = self + ret = append(ret, &samls[i]) + } + return ret, nil +} + +func (self *SHuaweiClient) DeleteSAMLProvider(id string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + _, err = client.SAMLProviders.Delete(id, nil) + return err +} + +func (self *SHuaweiClient) CreateSAMLProvider(opts *cloudprovider.SAMLProviderCreateOptions) (*SAMLProvider, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + params := jsonutils.Marshal(map[string]interface{}{ + "identity_provider": map[string]interface{}{ + "description": opts.Name, + "enabled": true, + }, + }) + name := []byte{} + for _, c := range opts.Name { + if unicode.IsLetter(c) || unicode.IsNumber(c) || c == '-' || c == '_' { + name = append(name, byte(c)) + } else { + name = append(name, '-') + } + } + opts.Name = string(name) + _, err = client.SAMLProviders.Update(opts.Name, params) + if err != nil { + if he, ok := err.(*modules.HuaweiClientError); ok && he.Code != 409 { + return nil, errors.Wrapf(err, "SAMLProviders.Update") + } + } + ret := SAMLProvider{client: self, Id: opts.Name} + err = self.UpdateSAMLProviderMetadata(opts.Name, opts.Metadata.String()) + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + err = self.InitSAMLProviderMapping(opts.Name) + if err != nil { + return nil, errors.Wrapf(err, "InitSAMLProviderMapping") + } + return &ret, nil +} + +type SAMLProviderMapping struct { + Id string + Rules jsonutils.JSONObject +} + +const ( + DEFAULT_ONECLOUD_MAPPING = "yunion-onecloud-mapping" +) + +var ( + onecloudMappingRules = jsonutils.Marshal(map[string]interface{}{ + "rules": []map[string]interface{}{ + { + "remote": []map[string]interface{}{ + { + "type": "User", + }, + { + "type": "Groups", + }, + }, + "local": []map[string]interface{}{ + { + "groups": "{1}", + "user": map[string]string{"name": "{0}"}, + }, + }, + }, + }, + }) +) + +func (self *SHuaweiClient) ListSAMLProviderMappings() ([]SAMLProviderMapping, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + mappings := []SAMLProviderMapping{} + err = doListAllWithNextLink(client.SAMLProviderMappings.List, nil, &mappings) + if err != nil { + return nil, err + } + return mappings, nil +} + +func (self *SHuaweiClient) InitSAMLProviderMapping(spId string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + + mappings, err := self.ListSAMLProviderMappings() + if err != nil { + return errors.Wrapf(err, "ListSAMLProviderMappings") + } + params := map[string]interface{}{ + "mapping": onecloudMappingRules, + } + + find := false + for i := range mappings { + if mappings[i].Id == DEFAULT_ONECLOUD_MAPPING { + find = true + } + } + if !find { + _, err = client.SAMLProviderMappings.Update(DEFAULT_ONECLOUD_MAPPING, jsonutils.Marshal(params)) + if err != nil { + return errors.Wrapf(err, "create mapping") + } + } + protocols, err := self.GetSAMLProviderProtocols(spId) + if err != nil { + return errors.Wrapf(err, "GetSAMLProviderProtocols") + } + params = map[string]interface{}{ + "protocol": map[string]string{ + "mapping_id": DEFAULT_ONECLOUD_MAPPING, + }, + } + for i := range protocols { + if protocols[i].Id == "saml" { + if protocols[i].MappingId == DEFAULT_ONECLOUD_MAPPING { + return nil + } + _, err = client.SAMLProviders.PatchInContextWithSpec(nil, spId, "protocols/saml", jsonutils.Marshal(params), "") + return err + } + } + _, err = client.SAMLProviders.UpdateInContextWithSpec(nil, spId, "protocols/saml", jsonutils.Marshal(params), "") + return err +} diff --git a/pkg/multicloud/huawei/shell/saml_provider.go b/pkg/multicloud/huawei/shell/saml_provider.go new file mode 100644 index 0000000000..273e45e4a7 --- /dev/null +++ b/pkg/multicloud/huawei/shell/saml_provider.go @@ -0,0 +1,89 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huawei" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SAMLProviderListOptions struct { + } + shellutils.R(&SAMLProviderListOptions{}, "saml-provider-list", "List saml provider", func(cli *huawei.SRegion, args *SAMLProviderListOptions) error { + result, err := cli.GetClient().ListSAMLProviders() + if err != nil { + return err + } + printList(result, 0, 0, 0, nil) + return nil + }) + + type SAMLProviderIdOptions struct { + ID string + } + + shellutils.R(&SAMLProviderIdOptions{}, "saml-provider-delete", "Delete saml provider", func(cli *huawei.SRegion, args *SAMLProviderIdOptions) error { + return cli.GetClient().DeleteSAMLProvider(args.ID) + }) + + shellutils.R(&SAMLProviderIdOptions{}, "saml-provider-protocol-list", "List saml provider protocol", func(cli *huawei.SRegion, args *SAMLProviderIdOptions) error { + result, err := cli.GetClient().GetSAMLProviderProtocols(args.ID) + if err != nil { + return err + } + printList(result, 0, 0, 0, nil) + return nil + }) + + shellutils.R(&SAMLProviderIdOptions{}, "saml-provider-metadata-show", "Show saml provider metadata", func(cli *huawei.SRegion, args *SAMLProviderIdOptions) error { + result, err := cli.GetClient().GetSAMLProviderMetadata(args.ID) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type SAMLProviderMetadataOptions struct { + ID string + METADATA string + } + + shellutils.R(&SAMLProviderMetadataOptions{}, "saml-provider-metadata-update", "Update saml provider metadata", func(cli *huawei.SRegion, args *SAMLProviderMetadataOptions) error { + return cli.GetClient().UpdateSAMLProviderMetadata(args.ID, args.METADATA) + }) + + type MappingListOptions struct { + } + + shellutils.R(&MappingListOptions{}, "saml-provider-mapping-list", "List saml provider mapping", func(cli *huawei.SRegion, args *MappingListOptions) error { + mappings, err := cli.GetClient().ListSAMLProviderMappings() + if err != nil { + return err + } + printList(mappings, 0, 0, 0, nil) + return nil + }) + + type MappingInitOptions struct { + SAML_PROVIDER string + } + + shellutils.R(&MappingInitOptions{}, "saml-provider-mapping-init", "Init saml provider mapping", func(cli *huawei.SRegion, args *MappingInitOptions) error { + return cli.GetClient().InitSAMLProviderMapping(args.SAML_PROVIDER) + }) + +} diff --git a/pkg/multicloud/qcloud/cam_role.go b/pkg/multicloud/qcloud/cam_role.go index 8e039948c9..9fa911ab12 100644 --- a/pkg/multicloud/qcloud/cam_role.go +++ b/pkg/multicloud/qcloud/cam_role.go @@ -16,9 +16,15 @@ package qcloud import ( "fmt" + "strconv" + "strings" "time" + "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) const ( @@ -37,7 +43,18 @@ const ( "UpdateTime": "2020-08-11 17:03:30" */ +type SPrincipal struct { + Federated []string +} + +type Statement struct { + Action string + Effect string + Principal SPrincipal +} + type SRole struct { + multicloud.SResourceBase client *SQcloudClient AddTime time.Time @@ -51,6 +68,92 @@ type SRole struct { UpdateTime time.Time } +func (self *SRole) GetGlobalId() string { + return self.RoleName +} + +func (self *SRole) GetName() string { + return self.RoleName +} + +func (self *SRole) GetDocument() *jsonutils.JSONDict { + if len(self.PolicyDocument) > 0 { + document, err := jsonutils.Parse([]byte(self.PolicyDocument)) + if err != nil { + return nil + } + return document.(*jsonutils.JSONDict) + } + return nil +} + +func (self *SRole) GetSAMLProvider() string { + document := self.GetDocument() + if document != nil { + statements := []Statement{} + document.Unmarshal(&statements, "statement") + for i := range statements { + if statements[i].Action == "name/sts:AssumeRoleWithSAML" { + for _, federated := range statements[i].Principal.Federated { + if strings.Contains(federated, ":saml-provider/") { + info := strings.Split(federated, "/") + return info[len(info)-1] + } + } + } + } + } + return "" +} + +func (self *SRole) Delete() error { + return self.client.DeleteRole(self.RoleName) +} + +func (self *SRole) GetICloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + ret := []cloudprovider.ICloudpolicy{} + for { + part, total, err := self.client.ListAttachedRolePolicies(self.RoleName, "", len(ret), 50) + if err != nil { + return nil, errors.Wrapf(err, "ListAttachedRolePolicies") + } + for i := range part { + part[i].client = self.client + ret = append(ret, &part[i]) + } + if len(ret) >= total { + break + } + } + return ret, nil +} + +func (self *SRole) AttachPolicy(id string) error { + return self.client.AttachRolePolicy(self.RoleName, id) +} + +func (self *SRole) DetachPolicy(id string) error { + return self.client.DetachRolePolicy(self.RoleName, id) +} + +func (self *SQcloudClient) GetICloudroles() ([]cloudprovider.ICloudrole, error) { + ret := []cloudprovider.ICloudrole{} + for { + part, total, err := self.DescribeRoleList(len(ret), 200) + if err != nil { + return nil, errors.Wrapf(err, "DescribeRoleList") + } + for i := range part { + part[i].client = self + ret = append(ret, &part[i]) + } + if len(ret) >= total { + break + } + } + return ret, nil +} + func (self *SQcloudClient) DescribeRoleList(offset int, limit int) ([]SRole, int, error) { if limit < 1 || limit > 200 { limit = 200 @@ -76,6 +179,9 @@ func (self *SQcloudClient) DescribeRoleList(offset int, limit int) ([]SRole, int } func (self *SQcloudClient) CreateRole(name, document, desc string) (*SRole, error) { + if len(document) == 0 { + document = DEFAULT_ROLE_DOCUMENT + } params := map[string]string{ "RoleName": name, "PolicyDocument": document, @@ -145,7 +251,11 @@ func (self *SQcloudClient) DeleteRole(name string) error { func (self *SQcloudClient) AttachRolePolicy(roleName string, policyId string) error { params := map[string]string{ "AttachRoleName": roleName, - "PolicyId": policyId, + } + if _id, _ := strconv.Atoi(policyId); _id > 0 { + params["PolicyId"] = policyId + } else { + params["PolicyName"] = policyId } _, err := self.camRequest("AttachRolePolicy", params) return err @@ -154,7 +264,11 @@ func (self *SQcloudClient) AttachRolePolicy(roleName string, policyId string) er func (self *SQcloudClient) DetachRolePolicy(roleName string, policyId string) error { params := map[string]string{ "DetachRoleName": roleName, - "PolicyId": policyId, + } + if _id, _ := strconv.Atoi(policyId); _id > 0 { + params["PolicyId"] = policyId + } else { + params["PolicyName"] = policyId } _, err := self.camRequest("DetachRolePolicy", params) return err diff --git a/pkg/multicloud/qcloud/cam_user.go b/pkg/multicloud/qcloud/cam_user.go index 7c9f533331..2addfc0f38 100644 --- a/pkg/multicloud/qcloud/cam_user.go +++ b/pkg/multicloud/qcloud/cam_user.go @@ -55,7 +55,7 @@ func (user *SUser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, erro } ret := []cloudprovider.ICloudpolicy{} for i := range policies { - if policies[i].PolicyType == "QCS" { + if policies[i].PolicyType == "QCS" || policies[i].PolicyType == "" { policies[i].client = user.client ret = append(ret, &policies[i]) } @@ -79,7 +79,7 @@ func (user *SUser) GetICustomCloudpolicies() ([]cloudprovider.ICloudpolicy, erro } ret := []cloudprovider.ICloudpolicy{} for i := range policies { - if policies[i].PolicyType != "QCS" { + if policies[i].PolicyType == "User" { policies[i].client = user.client ret = append(ret, &policies[i]) } diff --git a/pkg/multicloud/qcloud/provider/provider.go b/pkg/multicloud/qcloud/provider/provider.go index 8d98e41ede..1a94641255 100644 --- a/pkg/multicloud/qcloud/provider/provider.go +++ b/pkg/multicloud/qcloud/provider/provider.go @@ -370,16 +370,58 @@ func (self *SQcloudProvider) GetSamlEntityId() string { return cloudprovider.SAML_ENTITY_ID_QCLOUD } -func (self *SQcloudProvider) GetSamlSpInitiatedLoginUrl(idpName string) string { - return self.client.GetSamlSpInitiatedLoginUrl(idpName) -} - func (self *SQcloudProvider) GetICloudDnsZones() ([]cloudprovider.ICloudDnsZone, error) { return self.client.GetICloudDnsZones() } + func (self *SQcloudProvider) GetICloudDnsZoneById(id string) (cloudprovider.ICloudDnsZone, error) { return self.client.GetDomainById(id) } + func (self *SQcloudProvider) CreateICloudDnsZone(opts *cloudprovider.SDnsZoneCreateOptions) (cloudprovider.ICloudDnsZone, error) { return self.client.CreateICloudDnsZone(opts) } + +func (self *SQcloudProvider) CreateICloudSAMLProvider(opts *cloudprovider.SAMLProviderCreateOptions) (cloudprovider.ICloudSAMLProvider, error) { + saml, err := self.client.CreateSAMLProvider(opts.Name, opts.Metadata.String(), "") + if err != nil { + return nil, errors.Wrap(err, "CreateSAMLProvider") + } + return saml, nil +} + +func (self *SQcloudProvider) GetICloudSAMLProviders() ([]cloudprovider.ICloudSAMLProvider, error) { + return self.client.GetICloudSAMLProviders() +} + +func (self *SQcloudProvider) CreateICloudrole(opts *cloudprovider.SRoleCreateOptions) (cloudprovider.ICloudrole, error) { + if len(opts.SAMLProvider) > 0 { + document := fmt.Sprintf(`{"version":"2.0","statement":[{"action":"name/sts:AssumeRoleWithSAML","effect":"allow","principal":{"federated":["qcs::cam::uin/%s:saml-provider/%s"]},"condition":{}}]}`, self.client.GetAccountId(), opts.SAMLProvider) + role, err := self.client.CreateRole(opts.Name, document, opts.Desc) + if err != nil { + return nil, errors.Wrapf(err, "CreateRole") + } + return role, nil + } + role, err := self.client.CreateRole(opts.Name, "", opts.Desc) + if err != nil { + return nil, errors.Wrapf(err, "") + } + return role, nil +} + +func (self *SQcloudProvider) GetICloudroles() ([]cloudprovider.ICloudrole, error) { + return self.client.GetICloudroles() +} + +func (self *SQcloudProvider) GetICloudroleByName(name string) (cloudprovider.ICloudrole, error) { + role, err := self.client.GetRole(name) + if err != nil { + return nil, errors.Wrapf(err, "GetRole(%s)", name) + } + return role, nil +} + +func (self *SQcloudProvider) GetICloudroleById(id string) (cloudprovider.ICloudrole, error) { + return self.GetICloudroleByName(id) +} diff --git a/pkg/multicloud/qcloud/qcloud.go b/pkg/multicloud/qcloud/qcloud.go index 81452de963..cdb3ec7764 100644 --- a/pkg/multicloud/qcloud/qcloud.go +++ b/pkg/multicloud/qcloud/qcloud.go @@ -955,7 +955,3 @@ func (self *SQcloudClient) GetCapabilities() []string { } return caps } - -func (self *SQcloudClient) GetSamlSpInitiatedLoginUrl(idpName string) string { - return fmt.Sprintf("https://cloud.tencent.com/login/forwardIdp/%s/%s", self.ownerName, idpName) -} diff --git a/pkg/multicloud/qcloud/saml_provider.go b/pkg/multicloud/qcloud/saml_provider.go new file mode 100644 index 0000000000..7e9432d933 --- /dev/null +++ b/pkg/multicloud/qcloud/saml_provider.go @@ -0,0 +1,179 @@ +// 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 qcloud + +import ( + "encoding/base64" + "fmt" + "time" + "unicode" + + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/util/samlutils" +) + +type SAMLProvider struct { + multicloud.SResourceBase + client *SQcloudClient + + Name string + Description string + CreateTime time.Time + ModifyTime time.Time + SAMLMetadata string +} + +func (self *SAMLProvider) GetId() string { + return self.Name +} + +func (self *SAMLProvider) GetGlobalId() string { + return self.Name +} + +func (self *SAMLProvider) GetName() string { + return self.Name +} + +func (self *SAMLProvider) GetStatus() string { + return api.SAML_PROVIDER_STATUS_AVAILABLE +} + +func (self *SAMLProvider) Delete() error { + return self.client.DeleteSAMLProvider(self.Name) +} + +func (self *SAMLProvider) GetAuthUrl() string { + return fmt.Sprintf("https://cloud.tencent.com/login/forwardIdp/%s/%s", self.client.ownerName, self.Name) +} + +func (self *SAMLProvider) GetMetadataDocument() (*samlutils.EntityDescriptor, error) { + provider, err := self.client.GetSAMLProvider(self.Name) + if err != nil { + return nil, errors.Wrapf(err, "GetSAMLProvider(%s)", self.Name) + } + metadata, err := base64.StdEncoding.DecodeString(provider.SAMLMetadata) + if err != nil { + return nil, errors.Wrapf(err, "decode metadata") + } + ret, err := samlutils.ParseMetadata(metadata) + if err != nil { + return nil, errors.Wrapf(err, "ParseMetadata") + } + return &ret, nil +} + +func (self *SQcloudClient) ListSAMLProviders() ([]SAMLProvider, error) { + resp, err := self.camRequest("ListSAMLProviders", nil) + if err != nil { + return nil, errors.Wrapf(err, "ListSAMLProviders") + } + result := []SAMLProvider{} + err = resp.Unmarshal(&result, "SAMLProviderSet") + if err != nil { + return nil, errors.Wrapf(err, "Unmarshal") + } + return result, nil +} + +func (self *SQcloudClient) CreateSAMLProvider(name, metadata, desc string) (*SAMLProvider, error) { + if len(desc) == 0 { + desc = "For CloudId Service" + } + //支持3-128个数字、大小写字母、和+=,.@_- + name = func() string { + ret := "" + for _, c := range name { + if unicode.IsLetter(c) || unicode.IsNumber(c) || + c == '+' || c == '=' || c == ',' || c == '.' || c == '@' || c == '_' || c == '-' { + ret += string(c) + } else { + ret += "-" + } + } + return ret + }() + if len(name) > 128 { + name = name[:128] + } + params := map[string]string{ + "Name": name, + "Description": desc, + "SAMLMetadataDocument": base64.StdEncoding.EncodeToString([]byte(metadata)), + } + _, err := self.camRequest("CreateSAMLProvider", params) + if err != nil { + return nil, errors.Wrapf(err, "CreateSAMLProvider") + } + return self.GetSAMLProvider(name) +} + +func (self *SQcloudClient) GetSAMLProvider(name string) (*SAMLProvider, error) { + params := map[string]string{ + "Name": name, + } + resp, err := self.camRequest("GetSAMLProvider", params) + if err != nil { + return nil, errors.Wrapf(err, "GetSAMLProvider") + } + result := &SAMLProvider{client: self} + err = resp.Unmarshal(result) + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return result, nil +} + +func (self *SQcloudClient) DeleteSAMLProvider(name string) error { + params := map[string]string{ + "Name": name, + } + _, err := self.camRequest("DeleteSAMLProvider", params) + return err +} + +func (self *SQcloudClient) UpdateSAMLProvider(name, metadata, desc string) error { + params := map[string]string{ + "Name": name, + } + if len(desc) > 0 { + params["Description"] = desc + } + if len(metadata) > 0 { + params["SAMLMetadataDocument"] = base64.StdEncoding.EncodeToString([]byte(metadata)) + } + _, err := self.camRequest("UpdateSAMLProvider", params) + if err != nil { + return errors.Wrap(err, "UpdateSAMLProvider") + } + return nil +} + +func (self *SQcloudClient) GetICloudSAMLProviders() ([]cloudprovider.ICloudSAMLProvider, error) { + providers, err := self.ListSAMLProviders() + if err != nil { + return nil, errors.Wrapf(err, "ListSAMLProviders") + } + ret := []cloudprovider.ICloudSAMLProvider{} + for i := range providers { + providers[i].client = self + ret = append(ret, &providers[i]) + } + return ret, nil +} diff --git a/pkg/multicloud/qcloud/shell/saml_provider.go b/pkg/multicloud/qcloud/shell/saml_provider.go new file mode 100644 index 0000000000..370b561743 --- /dev/null +++ b/pkg/multicloud/qcloud/shell/saml_provider.go @@ -0,0 +1,76 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/qcloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SAMLProviderListOptions struct { + } + shellutils.R(&SAMLProviderListOptions{}, "saml-provider-list", "List saml provider", func(cli *qcloud.SRegion, args *SAMLProviderListOptions) error { + result, err := cli.GetClient().ListSAMLProviders() + if err != nil { + return err + } + printList(result, 0, 0, 0, nil) + return nil + }) + + type SAMLProviderCreateOptions struct { + NAME string + Desc string + METADATA string + } + + shellutils.R(&SAMLProviderCreateOptions{}, "saml-provider-create", "Create saml provider", func(cli *qcloud.SRegion, args *SAMLProviderCreateOptions) error { + result, err := cli.GetClient().CreateSAMLProvider(args.NAME, args.METADATA, args.Desc) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type SAMLProviderNameOptions struct { + NAME string + } + + shellutils.R(&SAMLProviderNameOptions{}, "saml-provider-show", "Show saml provider", func(cli *qcloud.SRegion, args *SAMLProviderNameOptions) error { + result, err := cli.GetClient().GetSAMLProvider(args.NAME) + if err != nil { + return err + } + printObject(result) + return nil + }) + + shellutils.R(&SAMLProviderNameOptions{}, "saml-provider-delete", "Delete saml provider", func(cli *qcloud.SRegion, args *SAMLProviderNameOptions) error { + return cli.GetClient().DeleteSAMLProvider(args.NAME) + }) + + type SAMLProviderUpdateOptions struct { + NAME string + Desc string + Metadata string + } + + shellutils.R(&SAMLProviderUpdateOptions{}, "saml-provider-update", "Update saml provider", func(cli *qcloud.SRegion, args *SAMLProviderUpdateOptions) error { + return cli.GetClient().UpdateSAMLProvider(args.NAME, args.Metadata, args.Desc) + }) + +} diff --git a/pkg/util/samlutils/idp/idp.go b/pkg/util/samlutils/idp/idp.go index ea46d0f34b..e9751e3b32 100644 --- a/pkg/util/samlutils/idp/idp.go +++ b/pkg/util/samlutils/idp/idp.go @@ -83,7 +83,11 @@ func (idp *SSAMLIdpInstance) AddHandlers(app *appsrv.Application, prefix string, handler = middleware(handler) } app.AddHandler("GET", idp.redirectLogoutPath, handler) - app.AddHandler("GET", idp.idpInitiatedSSOPath, idp.idpInitiatedSSOHandler) + handler = idp.idpInitiatedSSOHandler + if middleware != nil { + handler = middleware(handler) + } + app.AddHandler("GET", idp.idpInitiatedSSOPath, handler) log.Infof("IDP metadata: %s", idp.getMetadataUrl(IDP_ID_KEY)) log.Infof("IDP redirect login: %s", idp.getRedirectLoginUrl(IDP_ID_KEY)) @@ -141,7 +145,7 @@ func (idp *SSAMLIdpInstance) getIdpInitiatedSSOUrl() string { func (idp *SSAMLIdpInstance) metadataHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { params := appctx.AppContextParams(ctx) idpId := params[IDP_ID_KEY] - desc := idp.getMetadata(idpId) + desc := idp.GetMetadata(idpId) appsrv.SendXmlWithIndent(w, nil, desc, true) } @@ -187,7 +191,7 @@ func (idp *SSAMLIdpInstance) idpInitiatedSSOHandler(ctx context.Context, w http. appsrv.SendHTML(w, respHtml) } -func (idp *SSAMLIdpInstance) getMetadata(idpId string) samlutils.EntityDescriptor { +func (idp *SSAMLIdpInstance) GetMetadata(idpId string) samlutils.EntityDescriptor { input := samlutils.SSAMLIdpMetadataInput{ EntityId: idp.saml.GetEntityId(), CertString: idp.saml.GetCertString(), diff --git a/pkg/util/samlutils/sp/idp.go b/pkg/util/samlutils/sp/idp.go index 69df918127..70e6440c33 100644 --- a/pkg/util/samlutils/sp/idp.go +++ b/pkg/util/samlutils/sp/idp.go @@ -37,7 +37,7 @@ func NewSAMLIdp(entityId, redirectSsoUrl string) *SSAMLIdentityProvider { func NewSAMLIdpFromDescriptor(desc samlutils.EntityDescriptor) (*SSAMLIdentityProvider, error) { entityId := desc.EntityId - if desc.IDPSSODescriptor != nil { + if desc.IDPSSODescriptor == nil { return nil, errors.Wrap(httperrors.ErrInputParameter, "missing IDPSSODescriptor") } redirectSsoUrl := findSSOUrl(desc, samlutils.BINDING_HTTP_REDIRECT) diff --git a/pkg/util/samlutils/types.go b/pkg/util/samlutils/types.go index 96a45ff338..f407406cf6 100644 --- a/pkg/util/samlutils/types.go +++ b/pkg/util/samlutils/types.go @@ -240,6 +240,11 @@ type EntityDescriptor struct { Organization *Organization `xml:"Organization"` } +func (ed EntityDescriptor) String() string { + str, _ := xml.MarshalIndent(ed, "", " ") + return string(str) +} + type SIdpRedirectLoginInput struct { SAMLRequest string `json:"SAMLRequest,ignoreempty"` RelayState string `json:"RelayState,ignoreempty"`