mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-10-12 23:38:30 +08:00
fix(cloudid): omit clouduser secret from list and get responses (#25651)
Return console login details from the login-info action; skip secret in resource JSON and logs. Co-authored-by: Qiu Jian <qiujian@yunionyun.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Qiu Jian
Cursor
parent
23d7222bd0
commit
3c47ebba45
@@ -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位密码
|
||||
//
|
||||
|
||||
@@ -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"`
|
||||
// 手机号码
|
||||
|
||||
@@ -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"`
|
||||
// 手机号码
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user