diff --git a/pkg/apis/cloudid/clouduser.go b/pkg/apis/cloudid/clouduser.go index b6b1e79f88..6de37d6ee9 100644 --- a/pkg/apis/cloudid/clouduser.go +++ b/pkg/apis/cloudid/clouduser.go @@ -215,6 +215,13 @@ type ClouduserSyncInput struct { type ClouduserUpdateInput struct { } +type ClouduserLoginInfo struct { + Account string `json:"account"` + Username string `json:"username"` + Password string `json:"password"` + Url string `json:"url"` +} + type ClouduserResetPasswordInput struct { // 若此参数为空, 默认会生成随机12位密码 // diff --git a/pkg/apis/cloudid/zz_generated.model.go b/pkg/apis/cloudid/zz_generated.model.go index e871995450..afd0e34a01 100644 --- a/pkg/apis/cloudid/zz_generated.model.go +++ b/pkg/apis/cloudid/zz_generated.model.go @@ -109,7 +109,7 @@ type SClouduser struct { apis.SExternalizedResourceBase SCloudaccountResourceBase SCloudproviderResourceBase - Secret string `json:"secret"` + Secret string `json:"-"` // 是否可以控制台登录 IsConsoleLogin *bool `json:"is_console_login,omitempty"` // 手机号码 diff --git a/pkg/cloudid/models/clouduser.go b/pkg/cloudid/models/clouduser.go index 70a64911dd..aa20ccd929 100644 --- a/pkg/cloudid/models/clouduser.go +++ b/pkg/cloudid/models/clouduser.go @@ -71,7 +71,7 @@ type SClouduser struct { SCloudaccountResourceBase SCloudproviderResourceBase - Secret string `length:"0" charset:"ascii" nullable:"true" list:"user" create:"domain_optional"` + Secret string `length:"0" charset:"ascii" nullable:"true" create:"domain_optional" log:"skip"` // 是否可以控制台登录 IsConsoleLogin tristate.TriState `default:"false" list:"user" create:"optional"` // 手机号码 diff --git a/pkg/cloudid/models/clouduser_logininfo.go b/pkg/cloudid/models/clouduser_logininfo.go new file mode 100644 index 0000000000..d87440c037 --- /dev/null +++ b/pkg/cloudid/models/clouduser_logininfo.go @@ -0,0 +1,75 @@ +// 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" + "net/url" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + computeapi "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" +) + +func (self *SClouduser) GetDetailsLoginInfo(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*api.ClouduserLoginInfo, error) { + if len(self.Secret) == 0 { + return nil, httperrors.NewNotFoundError("No login secret found") + } + password, err := self.GetPassword() + if err != nil { + return nil, errors.Wrap(err, "GetPassword") + } + account, err := self.GetCloudaccount() + if err != nil { + return nil, errors.Wrap(err, "GetCloudaccount") + } + return formatClouduserLoginInfo(self.Name, account.Provider, account.IamLoginUrl, password), nil +} + +func formatClouduserLoginInfo(name, provider, iamLoginUrl, password string) *api.ClouduserLoginInfo { + username := name + account := "" + switch provider { + case computeapi.CLOUD_PROVIDER_ALIYUN: + suffix := strings.TrimPrefix(iamLoginUrl, "https://signin.aliyun.com/") + suffix = strings.TrimSuffix(suffix, "/login.htm") + if len(suffix) > 0 { + username = fmt.Sprintf("%s@%s", name, suffix) + account = suffix + } + case computeapi.CLOUD_PROVIDER_QCLOUD, computeapi.CLOUD_PROVIDER_HUAWEI: + u, _ := url.Parse(iamLoginUrl) + if u != nil { + account = u.Query().Get("account") + } + case computeapi.CLOUD_PROVIDER_AWS: + account = strings.TrimPrefix(iamLoginUrl, "https://") + if info := strings.Split(account, "."); len(info) > 0 { + account = info[0] + } + } + return &api.ClouduserLoginInfo{ + Account: account, + Username: username, + Password: password, + Url: iamLoginUrl, + } +} diff --git a/pkg/cloudid/models/clouduser_logininfo_test.go b/pkg/cloudid/models/clouduser_logininfo_test.go new file mode 100644 index 0000000000..4b76c02d19 --- /dev/null +++ b/pkg/cloudid/models/clouduser_logininfo_test.go @@ -0,0 +1,81 @@ +// 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 ( + "reflect" + "testing" + + "yunion.io/x/pkg/utils" + + computeapi "yunion.io/x/onecloud/pkg/apis/compute" +) + +func TestClouduserSecretFieldTags(t *testing.T) { + ft, ok := reflect.TypeOf(SClouduser{}).FieldByName("Secret") + if !ok { + t.Fatal("Secret field missing") + } + if got := ft.Tag.Get("list"); got != "" { + t.Fatalf("Secret list tag = %q, want empty", got) + } + if got := ft.Tag.Get("get"); got != "" { + t.Fatalf("Secret get tag = %q, want empty", got) + } +} + +func TestGetPassword(t *testing.T) { + plain := "P@ssw0rd12" + user := &SClouduser{} + user.Id = "clouduser-id" + sec, err := utils.EncryptAESBase64(user.Id, plain) + if err != nil { + t.Fatalf("EncryptAESBase64: %v", err) + } + user.Secret = sec + got, err := user.GetPassword() + if err != nil { + t.Fatalf("GetPassword: %v", err) + } + if got != plain { + t.Fatalf("GetPassword = %q, want %q", got, plain) + } +} + +func TestFormatClouduserLoginInfo(t *testing.T) { + info := formatClouduserLoginInfo("alice", computeapi.CLOUD_PROVIDER_ALIYUN, "https://signin.aliyun.com/mycorp.onaliyun.com/login.htm", "secret") + if info.Username != "alice@mycorp.onaliyun.com" { + t.Fatalf("aliyun username = %q", info.Username) + } + if info.Account != "mycorp.onaliyun.com" { + t.Fatalf("aliyun account = %q", info.Account) + } + if info.Password != "secret" || info.Url == "" { + t.Fatalf("unexpected login info: %+v", info) + } + + info = formatClouduserLoginInfo("bob", computeapi.CLOUD_PROVIDER_AWS, "https://123456789012.signin.aws.amazon.com/console", "secret") + if info.Account != "123456789012" { + t.Fatalf("aws account = %q", info.Account) + } + if info.Username != "bob" { + t.Fatalf("aws username = %q", info.Username) + } + + info = formatClouduserLoginInfo("carol", computeapi.CLOUD_PROVIDER_QCLOUD, "https://cloud.tencent.com/login?account=corp", "secret") + if info.Account != "corp" { + t.Fatalf("qcloud account = %q", info.Account) + } +} diff --git a/pkg/mcclient/modules/cloudid/mod_cloudusers.go b/pkg/mcclient/modules/cloudid/mod_cloudusers.go index afd99da21a..1a8ec3da26 100644 --- a/pkg/mcclient/modules/cloudid/mod_cloudusers.go +++ b/pkg/mcclient/modules/cloudid/mod_cloudusers.go @@ -15,16 +15,8 @@ package cloudid import ( - "fmt" - "net/url" - "strings" - "yunion.io/x/jsonutils" - "yunion.io/x/pkg/errors" - "yunion.io/x/pkg/utils" - api "yunion.io/x/onecloud/pkg/apis/compute" - "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modulebase" "yunion.io/x/onecloud/pkg/mcclient/modules" @@ -47,59 +39,5 @@ func init() { } func (this *SClouduserManager) GetLoginInfo(s *mcclient.ClientSession, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { - data, err := this.Get(s, id, nil) - if err != nil { - return nil, err - } - - user := struct { - Id string - Name string - Secret string - Provider string - IamLoginUrl string - }{} - - err = data.Unmarshal(&user) - if err != nil { - return nil, errors.Wrap(err, "data.Unmarshal") - } - - if len(user.Secret) == 0 { - return nil, httperrors.NewNotFoundError("No login secret found") - } - - password, err := utils.DescryptAESBase64(user.Id, user.Secret) - if err != nil { - return nil, errors.Wrap(err, "Descrypt") - } - - account := "" - - switch user.Provider { - case api.CLOUD_PROVIDER_ALIYUN: - suffix := strings.TrimPrefix(user.IamLoginUrl, "https://signin.aliyun.com/") - suffix = strings.TrimSuffix(suffix, "/login.htm") - if len(suffix) > 0 { - user.Name = fmt.Sprintf("%s@%s", user.Name, suffix) - account = suffix - } - case api.CLOUD_PROVIDER_QCLOUD, api.CLOUD_PROVIDER_HUAWEI: - u, _ := url.Parse(user.IamLoginUrl) - if u != nil { - account = u.Query().Get("account") - } - case api.CLOUD_PROVIDER_AWS: - account = strings.TrimPrefix(user.IamLoginUrl, "https://") - if info := strings.Split(account, "."); len(info) > 0 { - account = info[0] - } - } - - return jsonutils.Marshal(map[string]string{ - "account": account, - "username": user.Name, - "password": password, - "url": user.IamLoginUrl, - }), nil + return this.GetSpecific(s, id, "login-info", params) }