Files
cloudpods/pkg/apigateway/clientman/clientman.go
T
2019-08-09 21:11:46 +08:00

245 lines
5.8 KiB
Go

// 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 (
"fmt"
"os"
"path/filepath"
"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"
)
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)}
}
var (
TokenMan ITokenManagerV2
)
func InitClient(dbPath string) error {
if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
return errors.Wrapf(err, "ensure sqlite dir %s", filepath.Dir(dbPath))
}
if err := initDB("sqlite3", dbPath); err != nil {
return errors.Wrap(err, "init sqlite db")
}
info := auth.NewAuthInfo(options.Options.AuthURL,
options.Options.AdminDomain,
options.Options.AdminUser,
options.Options.AdminPassword,
options.Options.AdminProject,
options.Options.AdminProjectDomain,
)
auth.Init(info, options.Options.DebugClient, true,
options.Options.SslCertfile, options.Options.SslKeyfile)
TokenMan = NewMapTokenManagerV2()
return nil
}