Files
cloudpods/pkg/keystone/models/tokencache.go

210 lines
6.4 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 models
import (
"context"
"fmt"
"sort"
"strings"
"time"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/timeutils"
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/identity"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/keystone/cache"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/logclient"
)
var TokenCacheManager *STokenCacheManager
func init() {
TokenCacheManager = &STokenCacheManager{
SStandaloneAnonResourceBaseManager: db.NewStandaloneAnonResourceBaseManager(
STokenCache{},
"token_cache_tbl",
"token_cache",
"token_caches",
),
}
TokenCacheManager.SetVirtualObject(TokenCacheManager)
TokenCacheManager.TableSpec().AddIndex(false, "deleted", "valid")
}
type STokenCache struct {
db.SStandaloneAnonResourceBase
// Token string `width:"700" charset:"ascii" nullable:"false" primary:"true"`
ExpiredAt time.Time `nullable:"false"`
Valid bool
Method string `width:"32" charset:"ascii"`
AuditIds string `width:"512" charset:"utf8" index:"true"`
UserId string `width:"128" charset:"ascii" nullable:"false"`
ProjectId string `width:"128" charset:"ascii" nullable:"true"`
DomainId string `width:"128" charset:"ascii" nullable:"true"`
Source string `width:"16" charset:"ascii"`
Ip string `width:"64" charset:"ascii"`
}
type STokenCacheManager struct {
db.SStandaloneAnonResourceBaseManager
}
func joinAuditIds(ids []string) string {
sort.Strings(ids)
return strings.Join(ids, ",")
}
func (manager *STokenCacheManager) Save(ctx context.Context, tokenStr string, expiredAt time.Time, method string, auditIds []string, userId, projId, domainId, source, ip string) error {
return manager.insert(ctx, tokenStr, expiredAt, true, method, auditIds, userId, projId, domainId, source, ip)
}
func (manager *STokenCacheManager) Invalidate(ctx context.Context, userCred mcclient.TokenCredential, tokenStr string) error {
token, err := manager.FetchToken(tokenStr)
if err != nil {
return errors.Wrap(err, "FetchToken")
}
err = token.invalidate(ctx, userCred)
if err != nil {
return errors.Wrap(err, "token.invalidate")
}
return nil
}
func (manager *STokenCacheManager) BatchInvalidateByUserId(ctx context.Context, userCred mcclient.TokenCredential, uid string) error {
return manager.batchInvalidateInternal(ctx, userCred, func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
q = q.Equals("user_id", uid)
return q
})
}
func (manager *STokenCacheManager) BatchInvalidate(ctx context.Context, userCred mcclient.TokenCredential, method string, auditIds []string) error {
return manager.batchInvalidateInternal(ctx, userCred, func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
q = q.Equals("method", method).Equals("audit_ids", joinAuditIds(auditIds))
return q
})
}
func (manager *STokenCacheManager) batchInvalidateInternal(ctx context.Context, userCred mcclient.TokenCredential, filter func(q *sqlchemy.SQuery) *sqlchemy.SQuery) error {
q := manager.Query().IsTrue("valid")
q = filter(q)
tokens := make([]STokenCache, 0)
err := db.FetchModelObjects(manager, q, &tokens)
if err != nil {
return errors.Wrap(err, "FetchModelObjects")
}
if len(tokens) == 0 {
return nil
}
errs := make([]error, 0)
for i := range tokens {
token := tokens[i]
err := token.invalidate(ctx, userCred)
if err != nil {
errs = append(errs, errors.Wrapf(err, "batchInvalidateInternal token %s", token.Id))
}
}
if len(errs) > 0 {
return errors.NewAggregate(errs)
}
return nil
}
func (manager *STokenCacheManager) insert(ctx context.Context, token string, expiredAt time.Time, valid bool, method string, auditIds []string, userId, projectId, domainId, source, ip string) error {
val := STokenCache{
SStandaloneAnonResourceBase: db.SStandaloneAnonResourceBase{
Id: token,
},
ExpiredAt: expiredAt,
Valid: valid,
Method: method,
AuditIds: joinAuditIds(auditIds),
UserId: userId,
ProjectId: projectId,
DomainId: domainId,
Source: source,
Ip: ip,
}
val.SetModelManager(manager, &val)
err := manager.TableSpec().InsertOrUpdate(ctx, &val)
return errors.Wrap(err, "InsertOrUpdate")
}
func (manager *STokenCacheManager) FetchToken(tokenStr string) (*STokenCache, error) {
obj, err := manager.FetchById(tokenStr)
if err != nil {
return nil, errors.Wrap(err, "FetchById")
}
return obj.(*STokenCache), nil
}
func (manager *STokenCacheManager) removeObsolete() error {
sql := fmt.Sprintf("DELETE FROM %s WHERE expired_at < ?", manager.TableSpec().Name())
db := sqlchemy.GetDBWithName(manager.TableSpec().GetDBName())
now := timeutils.UtcNow()
_, err := db.Exec(sql, now.Add(-24*time.Hour))
return errors.Wrap(err, "Exec Delete")
}
func RemoveObsoleteInvalidTokens(ctx context.Context, userCred mcclient.TokenCredential, start bool) {
err := TokenCacheManager.removeObsolete()
if err != nil {
log.Errorf("RemoveObsoleteInvalidTokens fail %s", err)
}
}
func (manager *STokenCacheManager) FetchInvalidTokens() ([]string, error) {
q := manager.Query("id").IsFalse("valid")
tokens := make([]STokenCache, 0)
err := db.FetchModelObjects(manager, q, &tokens)
if err != nil {
return nil, errors.Wrap(err, "FetchModelObjects")
}
ret := make([]string, len(tokens))
for i := range tokens {
ret[i] = tokens[i].Id
}
return ret, nil
}
func (token *STokenCache) invalidate(ctx context.Context, userCred mcclient.TokenCredential) error {
err := TokenCacheManager.BatchInvalidate(ctx, userCred, api.AUTH_METHOD_TOKEN, []string{token.Id})
if err != nil {
return errors.Wrapf(err, "BatchInvalidate subtoken %s", token.Id)
}
_, err = db.Update(token, func() error {
token.Valid = false
return nil
})
if err != nil {
return errors.Wrap(err, "update")
}
cache.Remove(token.Id)
logclient.AddActionLogWithContext(ctx, token, logclient.ACT_DELETE, token.GetShortDesc(ctx), userCred, true)
return nil
}