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

245 lines
5.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}