diff --git a/pkg/apigateway/clientman/clientman.go b/pkg/apigateway/clientman/clientman.go index 8326d974f7..4ab3d51135 100644 --- a/pkg/apigateway/clientman/clientman.go +++ b/pkg/apigateway/clientman/clientman.go @@ -15,207 +15,15 @@ package clientman import ( - "fmt" "io/ioutil" - "strings" - "time" - "github.com/golang-plus/uuid" "github.com/pkg/errors" - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/onecloud/pkg/apigateway/options" - "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/util/seclib2" ) -func authVersion() string { - pos := strings.LastIndexByte(options.Options.AuthURL, '/') - if pos > 0 { - return options.Options.AuthURL[pos+1:] - } else { - return "" - } -} - -/* - mapTokenManagerV2 在mapTokenManager的基础上增加了 token持久化.后端为sqlite - token存储了两份:分别位于缓存和后端sqlite. - save同时会从sqlite中清理过期的缓存 -*/ -type ITokenManagerV2 interface { - mcclient.TokenManager - ITotpManager -} - -type credential struct { - Token mcclient.TokenCredential - Totp ITotp // 双因子认证。只有options totp enable的情况下该字段才有意义 -} - -type SMapTokenManagerV2 struct { - table map[string]*credential -} - -func UnmarshalV2Token(rbody jsonutils.JSONObject) (cred mcclient.TokenCredential, err error) { - access, err := rbody.Get("access") - if err == nil { - cred = &mcclient.TokenCredentialV2{} - err = access.Unmarshal(cred) - if err != nil { - err = fmt.Errorf("Invalid response when unmarshal V2 Token: %s", err) - } - - return - } - err = fmt.Errorf("Invalid response: no access object") - return -} - -func UnmarshalV3Token(rbody jsonutils.JSONObject, tokenId string) (cred mcclient.TokenCredential, err error) { - cred = &mcclient.TokenCredentialV3{Id: tokenId} - err = rbody.Unmarshal(cred) - if err != nil { - err = fmt.Errorf("Invalid response when unmarshal V3 Token: %v", err) - } - - return -} - -func (this *SMapTokenManagerV2) save(token mcclient.TokenCredential) string { - key, e := uuid.NewV4() - if e != nil { - log.Fatalf("uuid.NewV4 returns error!") - } - - var totp ITotp - kkey := key.String() - totp = &STotp{} - this.table[kkey] = &credential{Token: token, Totp: totp} - // 存储到后端服务 - go func() { - t := jsonutils.Marshal(token).String() - t2 := jsonutils.Marshal(totp).String() - tr := TokenRecord{ - TokenID: kkey, - Token: t, - TokenStr: token.GetTokenString(), - Totp: t2, - Version: authVersion(), - ExpireAt: token.GetExpires(), - } - if DB.Create(&tr); DB.Error != nil { - log.Errorf("%v", DB.Error) - } else { - // 删除已经过期的token避免无效token堆积 - now := time.Now() - if DB.Delete(TokenRecord{}, "expire_at < ?", now); DB.Error != nil { - log.Errorf("%v", DB.Error) - } - } - }() - return kkey -} - -func (this *SMapTokenManagerV2) get(tid string) mcclient.TokenCredential { - if cred := this.table[tid]; cred != nil && cred.Token != nil { - return cred.Token - } - - // load from db - err := this.loadFromDB(tid) - if err != nil { - log.Errorf("loadFromDB by tid %s: %v", tid, err) - return nil - } else { - return this.table[tid].Token - } -} - -func (this *SMapTokenManagerV2) remove(tid string) { - delete(this.table, tid) - if DB.Delete(TokenRecord{}, "token_id = ?", tid); DB.Error != nil { - log.Errorf("%v", DB.Error) - } -} - -func (this *SMapTokenManagerV2) getTotp(tid string) ITotp { - if cred := this.table[tid]; cred != nil && cred.Totp != nil { - return cred.Totp - } - - // load from db - err := this.loadFromDB(tid) - if err != nil { - log.Errorf(err.Error()) - return nil - } else { - return this.table[tid].Totp - } -} - -func (this *SMapTokenManagerV2) saveTotp(tid string) { - totp := this.table[tid].Totp - if totp == nil { - log.Errorf("%s totp is nil", tid) - return - } - - // 存储到后端服务 - go func() { - tr := TokenRecord{} - if DB.Where("token_id = ?", tid).First(&tr); DB.Error != nil { - log.Errorf("%v", DB.Error) - } else { - DB.Model(&tr).Update("Totp", jsonutils.Marshal(totp).String()) - } - }() -} - -func (this *SMapTokenManagerV2) loadFromDB(tid string) error { - // load from db - tr := TokenRecord{} - if DB.Where("token_id = ?", tid).First(&tr); DB.Error != nil { - return fmt.Errorf("%v", DB.Error) - } - - tjson, err := jsonutils.ParseString(tr.Token) - if err != nil { - return errors.Wrapf(err, "parse token to json: %s", tr.Token) - } - - var token mcclient.TokenCredential - if authVersion() == "v3" { - if token, err = UnmarshalV3Token(tjson, tr.TokenStr); err != nil { - return err - } - } else { - if token, err = UnmarshalV2Token(tjson); err != nil { - return err - } - } - - var totp STotp - totpJson, err := jsonutils.ParseString(tr.Totp) - if err != nil { - return err - } - - if err := totpJson.Unmarshal(&totp); err != nil { - return err - } - - this.table[tid] = &credential{Token: token, Totp: &totp} - return nil -} - -/*func NewMapTokenManagerV2() ITokenManagerV2 { - return &SMapTokenManagerV2{table: make(map[string]*credential)} -}*/ - func InitClient() error { info := auth.NewAuthInfo(options.Options.AuthURL, options.Options.AdminDomain, diff --git a/pkg/apigateway/clientman/db.go b/pkg/apigateway/clientman/db.go deleted file mode 100644 index a18d385347..0000000000 --- a/pkg/apigateway/clientman/db.go +++ /dev/null @@ -1,52 +0,0 @@ -// 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 clientman - -import ( - "time" - - "github.com/jinzhu/gorm" - _ "github.com/jinzhu/gorm/dialects/sqlite" - "github.com/pkg/errors" - - "yunion.io/x/log" -) - -var DB *gorm.DB - -type TokenRecord struct { - ID uint `gorm:"primary_key"` - TokenID string `gorm:"not null;unique"` - Token string `gorm:"not null;unique"` - TokenStr string `gorm:"not null;DEFAULT:''"` - Totp string `gorm:"not null;DEFAULT:''"` - Version string `gorm:"not null;DEFAULT:'v2'"` - ExpireAt time.Time -} - -func initDB(dialect string, args ...interface{}) error { - if DB == nil { - db, err := gorm.Open(dialect, args...) - if err != nil { - return errors.Wrapf(err, "Init DB by args: %v", args) - } - // Migrate the schema - db.AutoMigrate(&TokenRecord{}) - DB = db - return nil - } - log.Warningf("DB: %s , Conn: %v already connected...", dialect, args) - return nil -} diff --git a/pkg/apigateway/clientman/totp.go b/pkg/apigateway/clientman/totp.go index 24f20fb4f7..e068f3d37b 100644 --- a/pkg/apigateway/clientman/totp.go +++ b/pkg/apigateway/clientman/totp.go @@ -16,10 +16,6 @@ package clientman import ( "encoding/base32" - "fmt" - "time" - - "github.com/pquerna/otp/totp" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" @@ -27,69 +23,6 @@ import ( const MAX_OTP_RETRY = 5 // totp验证最大重试次数 -type ITotp interface { - UpdateRetryCount() - IsVerified() bool - MarkVerified() - VerifyTotpPasscode(s *mcclient.ClientSession, uid, passcode string) error -} - -type ITotpManager interface { - GetTotp(tid string) ITotp - SaveTotp(tid string) -} - -type STotp struct { - RetryCount int `json:"retry_count"` // 重试计数器 - LockExpireTime time.Time `json:"lock_expire_time"` // 锁定时间 - Verified bool `json:"verified"` // 验证状态 -} - -func (self *STotp) UpdateRetryCount() { - if self.RetryCount < MAX_OTP_RETRY { - self.RetryCount += 1 - - // 锁定 - if self.RetryCount >= MAX_OTP_RETRY { - self.LockExpireTime = time.Now().Add(30 * time.Second) - } - } else { - // 清零计数器,解除锁定 - if self.LockExpireTime.Before(time.Now()) { - self.LockExpireTime = time.Now() - self.RetryCount = 0 - } - } -} - -func (self *STotp) IsVerified() bool { - return self.Verified -} - -func (self *STotp) MarkVerified() { - self.Verified = true -} - -func (self *STotp) VerifyTotpPasscode(s *mcclient.ClientSession, uid, passcode string) error { - secret, err := fetchUserTotpCredSecret(s, uid) - if err != nil { - return fmt.Errorf("fetch totp secrets error: %s", err.Error()) - } - - releaseSeconds := self.LockExpireTime.Unix() - time.Now().Unix() - if releaseSeconds > 0 { - return fmt.Errorf("locked, retry after %d seconds", releaseSeconds) - } - - if totp.Validate(passcode, secret) { - self.MarkVerified() - return nil - } - - self.UpdateRetryCount() - return fmt.Errorf("invalid passcode") -} - // 获取用户TOTP credential 密码. func fetchUserTotpCredSecret(s *mcclient.ClientSession, uid string) (string, error) { secret, err := modules.Credentials.GetTotpSecret(s, uid) diff --git a/pkg/apigateway/options/options.go b/pkg/apigateway/options/options.go index d4e9ad9bc7..2fcb13e3fb 100644 --- a/pkg/apigateway/options/options.go +++ b/pkg/apigateway/options/options.go @@ -28,8 +28,6 @@ type GatewayOptions struct { EnableTotp bool `help:"Enable two-factor authentication" default:"true"` - // SqlitePath string `help:"sqlite db path" default:"/etc/yunion/data/yunionapi.db"` - common_options.CommonOptions `"request_worker_count->default":"32"` }