mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-08-30 17:13:08 +08:00
fix: shorten keystone token (#18222)
Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
This commit is contained in:
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
// 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 cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/util/hashcache"
|
||||
)
|
||||
|
||||
var (
|
||||
tokenCache *hashcache.Cache
|
||||
)
|
||||
|
||||
func Init(expire int) {
|
||||
tokenCache = hashcache.NewCache(2048, time.Duration(expire/2)*time.Second)
|
||||
}
|
||||
|
||||
func Save(tokenStr string, token interface{}) {
|
||||
tokenCache.AtomicSet(tokenStr, token)
|
||||
}
|
||||
|
||||
func Remove(tokenStr string) {
|
||||
tokenCache.AtomicRemove(tokenStr)
|
||||
}
|
||||
|
||||
func Get(tokenStr string) interface{} {
|
||||
return tokenCache.AtomicGet(tokenStr)
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
// 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 cache // import "yunion.io/x/onecloud/pkg/keystone/cache"
|
||||
@@ -373,7 +373,7 @@ func (cred *SCredential) Delete(ctx context.Context, userCred mcclient.TokenCred
|
||||
|
||||
if cred.Type == api.ACCESS_SECRET_TYPE {
|
||||
// clean tokens auth by this AKSK
|
||||
err := TokenCacheManager.BatchInvalidate(ctx, api.AUTH_METHOD_AKSK, []string{cred.Id})
|
||||
err := TokenCacheManager.BatchInvalidate(ctx, userCred, api.AUTH_METHOD_AKSK, []string{cred.Id})
|
||||
if err != nil {
|
||||
log.Errorf("BatchInvalidate token failed %s", err)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -28,14 +29,16 @@ import (
|
||||
|
||||
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{
|
||||
SModelBaseManager: db.NewModelBaseManager(
|
||||
SStandaloneAnonResourceBaseManager: db.NewStandaloneAnonResourceBaseManager(
|
||||
STokenCache{},
|
||||
"token_cache_tbl",
|
||||
"token_cache",
|
||||
@@ -46,17 +49,25 @@ func init() {
|
||||
}
|
||||
|
||||
type STokenCache struct {
|
||||
db.SModelBase
|
||||
db.SStandaloneAnonResourceBase
|
||||
|
||||
Token string `width:"700" charset:"ascii" nullable:"false" primary:"true"`
|
||||
// Token string `width:"64" charset:"ascii" nullable:"false" primary:"true"`
|
||||
ExpiredAt time.Time `nullable:"false"`
|
||||
Valid bool
|
||||
Method string `width:"32" charset:"ascii"`
|
||||
AuditIds string `width:"700" charset:"utf8" index:"true"`
|
||||
|
||||
Method string `width:"32" charset:"ascii"`
|
||||
AuditIds string `width:"256" 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.SModelBaseManager
|
||||
db.SStandaloneAnonResourceBaseManager
|
||||
}
|
||||
|
||||
func joinAuditIds(ids []string) string {
|
||||
@@ -64,88 +75,101 @@ func joinAuditIds(ids []string) string {
|
||||
return strings.Join(ids, ",")
|
||||
}
|
||||
|
||||
func (manager *STokenCacheManager) Save(ctx context.Context, token string, expiredAt time.Time, method string, auditIds []string) error {
|
||||
return manager.insert(ctx, token, expiredAt, true, method, auditIds)
|
||||
}
|
||||
|
||||
func (manager *STokenCacheManager) Invalidate(ctx context.Context, token string, expiredAt time.Time, method string, auditIds []string) error {
|
||||
return manager.insert(ctx, token, expiredAt, false, method, auditIds)
|
||||
}
|
||||
|
||||
func (manager *STokenCacheManager) BatchInvalidate(ctx context.Context, method string, auditIds []string) error {
|
||||
invalidQueue := []sCacheCredential{
|
||||
{
|
||||
Method: method,
|
||||
AuditIds: auditIds,
|
||||
},
|
||||
func (manager *STokenCacheManager) Save(ctx context.Context, tokenStr string, expiredAt time.Time, method string, auditIds []string, userId, projId, domainId, source, ip string) error {
|
||||
token, err := manager.FetchToken(tokenStr)
|
||||
if err != nil && errors.Cause(err) != sql.ErrNoRows {
|
||||
return errors.Wrap(err, "FetchToken")
|
||||
}
|
||||
for i := 0; i < len(invalidQueue); i++ {
|
||||
queues, err := manager.batchInvalidateInternal(ctx, invalidQueue[i])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "batchInvalidateInternal")
|
||||
}
|
||||
if len(queues) > 0 {
|
||||
invalidQueue = append(invalidQueue, queues...)
|
||||
}
|
||||
if token == nil || !token.Valid {
|
||||
return manager.insert(ctx, tokenStr, expiredAt, true, method, auditIds, userId, projId, domainId, source, ip)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type sCacheCredential struct {
|
||||
Method string
|
||||
AuditIds []string
|
||||
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) batchInvalidateInternal(ctx context.Context, cred sCacheCredential) ([]sCacheCredential, error) {
|
||||
q := manager.Query().Equals("method", cred.Method).Equals("audit_ids", joinAuditIds(cred.AuditIds))
|
||||
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 nil, errors.Wrap(err, "FetchModelObjects")
|
||||
return errors.Wrap(err, "FetchModelObjects")
|
||||
}
|
||||
if len(tokens) == 0 {
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
queues := make([]sCacheCredential, 0)
|
||||
errs := make([]error, 0)
|
||||
for i := range tokens {
|
||||
token := tokens[i]
|
||||
queues = append(queues, sCacheCredential{
|
||||
Method: api.AUTH_METHOD_TOKEN,
|
||||
AuditIds: []string{token.Token},
|
||||
})
|
||||
err := token.invalidate(ctx, userCred)
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "batchInvalidateInternal token %s", token.Id))
|
||||
}
|
||||
}
|
||||
err = manager.TableSpec().GetTableSpec().UpdateBatch(
|
||||
map[string]interface{}{
|
||||
"valid": false,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"method": cred.Method,
|
||||
"audit_ids": joinAuditIds(cred.AuditIds),
|
||||
},
|
||||
)
|
||||
return queues, errors.Wrap(err, "UpdateBatch")
|
||||
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) error {
|
||||
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{
|
||||
Token: token,
|
||||
SStandaloneAnonResourceBase: db.SStandaloneAnonResourceBase{
|
||||
Id: token,
|
||||
},
|
||||
ExpiredAt: expiredAt,
|
||||
Valid: valid,
|
||||
Method: method,
|
||||
AuditIds: joinAuditIds(auditIds),
|
||||
UserId: userId,
|
||||
ProjectId: projectId,
|
||||
DomainId: domainId,
|
||||
Source: source,
|
||||
Ip: ip,
|
||||
}
|
||||
err := manager.TableSpec().InsertOrUpdate(ctx, &val)
|
||||
return errors.Wrap(err, "InsertOrUpdate")
|
||||
}
|
||||
|
||||
func (manager *STokenCacheManager) IsValid(token string) (bool, error) {
|
||||
q := manager.Query().Equals("token", token)
|
||||
tokenCache := STokenCache{}
|
||||
err := q.First(&tokenCache)
|
||||
func (manager *STokenCacheManager) FetchToken(tokenStr string) (*STokenCache, error) {
|
||||
obj, err := manager.FetchById(tokenStr)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "Query")
|
||||
return nil, errors.Wrap(err, "FetchById")
|
||||
}
|
||||
return tokenCache.Valid, nil
|
||||
return obj.(*STokenCache), nil
|
||||
}
|
||||
|
||||
func (manager *STokenCacheManager) IsValid(tokenStr string) (bool, error) {
|
||||
token, err := manager.FetchToken(tokenStr)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "FetchToken")
|
||||
}
|
||||
return token.Valid, nil
|
||||
}
|
||||
|
||||
func (manager *STokenCacheManager) removeObsolete() error {
|
||||
@@ -164,7 +188,7 @@ func RemoveObsoleteInvalidTokens(ctx context.Context, userCred mcclient.TokenCre
|
||||
}
|
||||
|
||||
func (manager *STokenCacheManager) FetchInvalidTokens() ([]string, error) {
|
||||
q := manager.Query("token").IsFalse("valid")
|
||||
q := manager.Query("id").IsFalse("valid")
|
||||
tokens := make([]STokenCache, 0)
|
||||
err := db.FetchModelObjects(manager, q, &tokens)
|
||||
if err != nil {
|
||||
@@ -172,7 +196,28 @@ func (manager *STokenCacheManager) FetchInvalidTokens() ([]string, error) {
|
||||
}
|
||||
ret := make([]string, len(tokens))
|
||||
for i := range tokens {
|
||||
ret[i] = tokens[i].Token
|
||||
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
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ package models
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
@@ -838,10 +837,17 @@ func (user *SUser) PostUpdate(ctx context.Context, userCred mcclient.TokenCreden
|
||||
}
|
||||
logclient.AddActionLogWithContext(ctx, user, logclient.ACT_UPDATE_PASSWORD, nil, userCred, true)
|
||||
}
|
||||
if enabled, _ := data.Bool("enabled"); enabled {
|
||||
err := user.clearFailedAuth()
|
||||
if err != nil {
|
||||
log.Errorf("clearFailedAuth %s", err)
|
||||
if enabled, err := data.Bool("enabled"); err == nil {
|
||||
if enabled {
|
||||
err := user.clearFailedAuth()
|
||||
if err != nil {
|
||||
log.Errorf("clearFailedAuth %s", err)
|
||||
}
|
||||
} else {
|
||||
batchErr := TokenCacheManager.BatchInvalidateByUserId(ctx, userCred, user.Id)
|
||||
if batchErr != nil {
|
||||
log.Errorf("BatchInvalidateByUserId fail %s", batchErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -930,9 +936,12 @@ func (user *SUser) Delete(ctx context.Context, userCred mcclient.TokenCredential
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "PasswordManager.delete")
|
||||
}
|
||||
batchErr := TokenCacheManager.BatchInvalidate(ctx, api.AUTH_METHOD_PASSWORD, []string{fmt.Sprintf("%d", localUser.Id)})
|
||||
}
|
||||
|
||||
{
|
||||
batchErr := TokenCacheManager.BatchInvalidateByUserId(ctx, userCred, user.Id)
|
||||
if batchErr != nil {
|
||||
log.Errorf("BatchInvalidate fail %s", batchErr)
|
||||
log.Errorf("BatchInvalidateByUserId fail %s", batchErr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1336,3 +1345,22 @@ func (user *SUser) PerformEnable(
|
||||
}
|
||||
return user.SEnabledIdentityBaseResource.PerformEnable(ctx, userCred, query, input)
|
||||
}
|
||||
|
||||
func (user *SUser) PerformDisable(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
input apis.PerformDisableInput,
|
||||
) (jsonutils.JSONObject, error) {
|
||||
_, err := user.SEnabledIdentityBaseResource.PerformDisable(ctx, userCred, query, input)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "SEnabledIdentityBaseResource.PerformDisable")
|
||||
}
|
||||
{
|
||||
batchErr := TokenCacheManager.BatchInvalidateByUserId(ctx, userCred, user.Id)
|
||||
if batchErr != nil {
|
||||
log.Errorf("BatchInvalidateByUserId fail %s", batchErr)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
|
||||
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
|
||||
"yunion.io/x/onecloud/pkg/keystone/cache"
|
||||
"yunion.io/x/onecloud/pkg/keystone/cronjobs"
|
||||
"yunion.io/x/onecloud/pkg/keystone/models"
|
||||
"yunion.io/x/onecloud/pkg/keystone/options"
|
||||
@@ -90,6 +91,8 @@ func StartService() {
|
||||
|
||||
common_options.StartOptionManagerWithSessionDriver(opts, opts.ConfigSyncPeriodSeconds, api.SERVICE_TYPE, "", options.OnOptionsChange, models.NewServiceConfigSession())
|
||||
|
||||
cache.Init(opts.TokenExpirationSeconds)
|
||||
|
||||
if !opts.IsSlaveNode {
|
||||
cron := cronman.InitCronJobManager(true, opts.CronJobWorkerCount)
|
||||
|
||||
|
||||
@@ -46,25 +46,9 @@ func authUserByTokenV3(ctx context.Context, input mcclient.SAuthenticationInputV
|
||||
}
|
||||
|
||||
func authUserByToken(ctx context.Context, tokenStr string) (*api.SUserExtended, error) {
|
||||
valid, err := models.TokenCacheManager.IsValid(tokenStr)
|
||||
if err == nil {
|
||||
if !valid {
|
||||
return nil, errors.Wrap(httperrors.ErrInvalidCredential, "invalid token")
|
||||
} else {
|
||||
// passthrough
|
||||
}
|
||||
} else {
|
||||
if errors.Cause(err) != sql.ErrNoRows {
|
||||
return nil, errors.Wrap(err, "TokenCacheManager.IsValid")
|
||||
} else {
|
||||
// passthrough
|
||||
}
|
||||
}
|
||||
|
||||
token := SAuthToken{}
|
||||
err = token.ParseFernetToken(tokenStr)
|
||||
token, err := TokenStrDecode(tokenStr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "token.ParseFernetToken")
|
||||
return nil, errors.Wrap(err, "token.TokenStrDecode")
|
||||
}
|
||||
extUser, err := models.UserManager.FetchUserExtended(token.UserId, "", "", "")
|
||||
if err != nil {
|
||||
@@ -566,8 +550,6 @@ func AuthenticateV3(ctx context.Context, input mcclient.SAuthenticationInputV3)
|
||||
return nil, errors.Wrap(err, "getTokenV3")
|
||||
}
|
||||
|
||||
models.TokenCacheManager.Save(ctx, tokenV3.Id, token.ExpiresAt, token.Method, token.AuditIds)
|
||||
|
||||
return tokenV3, nil
|
||||
}
|
||||
|
||||
@@ -658,7 +640,5 @@ func _authenticateV2(ctx context.Context, input mcclient.SAuthenticationInputV2)
|
||||
return nil, errors.Wrap(err, "getTokenV2")
|
||||
}
|
||||
|
||||
models.TokenCacheManager.Save(ctx, tokenV2.Token.Id, token.ExpiresAt, token.Method, token.AuditIds)
|
||||
|
||||
return tokenV2, nil
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ package tokens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
@@ -28,6 +27,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/keystone/cache"
|
||||
"yunion.io/x/onecloud/pkg/keystone/models"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
@@ -136,20 +136,15 @@ func verifyTokensV2(ctx context.Context, w http.ResponseWriter, r *http.Request)
|
||||
params, _, _ := appsrv.FetchEnv(ctx, w, r)
|
||||
tokenStr := params["<token>"]
|
||||
|
||||
valid, err := models.TokenCacheManager.IsValid(tokenStr)
|
||||
if err == nil {
|
||||
if !valid {
|
||||
httperrors.InvalidCredentialError(ctx, w, "invalid token")
|
||||
cachedToken := cache.Get(tokenStr)
|
||||
if cachedToken != nil {
|
||||
if v2token, ok := cachedToken.(*mcclient.TokenCredentialV2); ok && v2token.IsValid() {
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Add(jsonutils.Marshal(v2token), "access")
|
||||
appsrv.SendJSON(w, ret)
|
||||
return
|
||||
} else {
|
||||
// passthrough
|
||||
}
|
||||
} else {
|
||||
if errors.Cause(err) != sql.ErrNoRows {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
} else {
|
||||
// passthrough
|
||||
cache.Remove(tokenStr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,20 +197,15 @@ type VerifyTokenV3Param struct {
|
||||
func verifyTokensV3(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
tokenStr := r.Header.Get(api.AUTH_SUBJECT_TOKEN_HEADER)
|
||||
|
||||
valid, err := models.TokenCacheManager.IsValid(tokenStr)
|
||||
if err == nil {
|
||||
if !valid {
|
||||
httperrors.InvalidCredentialError(ctx, w, "invalid token")
|
||||
cachedToken := cache.Get(tokenStr)
|
||||
if cachedToken != nil {
|
||||
if v3token, ok := cachedToken.(*mcclient.TokenCredentialV3); ok && v3token.IsValid() {
|
||||
w.Header().Set(api.AUTH_SUBJECT_TOKEN_HEADER, v3token.Id)
|
||||
v3token.Id = ""
|
||||
appsrv.SendJSON(w, jsonutils.Marshal(v3token))
|
||||
return
|
||||
} else {
|
||||
// passthrough
|
||||
}
|
||||
} else {
|
||||
if errors.Cause(err) != sql.ErrNoRows {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
} else {
|
||||
// passthrough
|
||||
cache.Remove(tokenStr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,12 +259,11 @@ func verifyCommon(ctx context.Context, w http.ResponseWriter, tokenStr string) (
|
||||
if adminToken.IsAllow(rbacscope.ScopeSystem, api.SERVICE_TYPE, "tokens", "perform", "auth").Result.IsDeny() {
|
||||
return nil, httperrors.NewForbiddenError("%s not allow to auth", adminToken.GetUserName())
|
||||
}
|
||||
token := SAuthToken{}
|
||||
err := token.ParseFernetToken(tokenStr)
|
||||
token, err := TokenStrDecode(tokenStr)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewInvalidCredentialError(errors.Wrapf(err, "invalid token").Error())
|
||||
}
|
||||
return &token, nil
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func authenticateToken(f appsrv.FilterHandler) appsrv.FilterHandler {
|
||||
|
||||
@@ -49,18 +49,20 @@ func invalidateToken(ctx context.Context, tokenStr string) error {
|
||||
if adminToken == nil || len(tokenStr) == 0 {
|
||||
return httperrors.NewForbiddenError("missing auth token")
|
||||
}
|
||||
if adminToken.IsAllow(rbacscope.ScopeSystem, api.SERVICE_TYPE, "tokens", "delete").Result.IsDeny() {
|
||||
return httperrors.NewForbiddenError("%s not allow to auth", adminToken.GetUserName())
|
||||
}
|
||||
token := SAuthToken{}
|
||||
err := token.ParseFernetToken(tokenStr)
|
||||
token, err := TokenStrDecode(tokenStr)
|
||||
if err != nil {
|
||||
return httperrors.NewInvalidCredentialError(errors.Wrapf(err, "invalid token").Error())
|
||||
}
|
||||
err = models.TokenCacheManager.Invalidate(ctx, tokenStr, token.ExpiresAt, token.Method, token.AuditIds)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Insert")
|
||||
|
||||
if adminToken.GetUserId() != token.UserId && adminToken.IsAllow(rbacscope.ScopeSystem, api.SERVICE_TYPE, "tokens", "delete").Result.IsDeny() {
|
||||
return httperrors.NewForbiddenError("%s not allow to auth", adminToken.GetUserName())
|
||||
}
|
||||
|
||||
err = models.TokenCacheManager.Invalidate(ctx, adminToken, tokenStr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Invalidate")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ package tokens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -25,10 +26,13 @@ import (
|
||||
"yunion.io/x/pkg/utils"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/identity"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/keystone/cache"
|
||||
"yunion.io/x/onecloud/pkg/keystone/keys"
|
||||
"yunion.io/x/onecloud/pkg/keystone/models"
|
||||
"yunion.io/x/onecloud/pkg/keystone/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -48,7 +52,7 @@ func GetDefaultToken() string {
|
||||
AuditIds: []string{utils.GenRequestId(16)},
|
||||
}
|
||||
var err error
|
||||
defaultAuthTokenStr, err = defaultAuthToken.EncodeFernetToken()
|
||||
defaultAuthTokenStr, err = defaultAuthToken.encodeFernetToken()
|
||||
if err != nil {
|
||||
log.Fatalf("defaultAuthToken.EncodeFernetToken fail: %s", err)
|
||||
}
|
||||
@@ -174,7 +178,42 @@ func (t *SAuthToken) Encode() ([]byte, error) {
|
||||
return t.getPayload().Encode()
|
||||
}
|
||||
|
||||
func (t *SAuthToken) ParseFernetToken(tokenStr string) error {
|
||||
func TokenStrDecode(tokenStr string) (*SAuthToken, error) {
|
||||
token, err := models.TokenCacheManager.FetchToken(tokenStr)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == sql.ErrNoRows {
|
||||
// not found
|
||||
token := &SAuthToken{}
|
||||
err := token.parseFernetToken(tokenStr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parseFernetToken")
|
||||
}
|
||||
return token, nil
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "FetchToken")
|
||||
}
|
||||
} else {
|
||||
if !token.Valid {
|
||||
return nil, errors.Wrap(httperrors.ErrInvalidCredential, "invalid token")
|
||||
}
|
||||
return &SAuthToken{
|
||||
UserId: token.UserId,
|
||||
ProjectId: token.ProjectId,
|
||||
DomainId: token.DomainId,
|
||||
|
||||
Method: token.Method,
|
||||
ExpiresAt: token.ExpiredAt,
|
||||
AuditIds: strings.Split(token.AuditIds, ","),
|
||||
|
||||
Context: mcclient.SAuthContext{
|
||||
Source: token.Source,
|
||||
Ip: token.Ip,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *SAuthToken) parseFernetToken(tokenStr string) error {
|
||||
tk := keys.TokenKeysManager.Decrypt([]byte(tokenStr)) // , time.Duration(options.Options.TokenExpirationSeconds)*time.Second)
|
||||
if tk == nil {
|
||||
return errors.Wrapf(ErrInvalidToken, tokenStr)
|
||||
@@ -189,7 +228,7 @@ func (t *SAuthToken) ParseFernetToken(tokenStr string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *SAuthToken) EncodeFernetToken() (string, error) {
|
||||
func (t *SAuthToken) encodeFernetToken() (string, error) {
|
||||
tk, err := t.Encode()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "encode error")
|
||||
@@ -201,6 +240,14 @@ func (t *SAuthToken) EncodeFernetToken() (string, error) {
|
||||
return string(ftk), nil
|
||||
}
|
||||
|
||||
func (t *SAuthToken) encodeShortToken() (string, error) {
|
||||
tk, err := t.encodeFernetToken()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "encodeFernetToken")
|
||||
}
|
||||
return stringutils2.GenId(tk), nil
|
||||
}
|
||||
|
||||
func (t *SAuthToken) GetSimpleUserCred(token string) (mcclient.TokenCredential, error) {
|
||||
userExt, err := models.UserManager.FetchUserExtended(t.UserId, "", "", "")
|
||||
if err != nil {
|
||||
@@ -286,7 +333,7 @@ func (t *SAuthToken) getTokenV3(
|
||||
token.Token.User.IsSystemAccount = user.IsSystemAccount
|
||||
token.Token.Context = t.Context
|
||||
|
||||
tk, err := t.EncodeFernetToken()
|
||||
tk, err := t.encodeShortToken()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "EncodeFernetToken")
|
||||
}
|
||||
@@ -356,9 +403,9 @@ func (t *SAuthToken) getTokenV3(
|
||||
}
|
||||
|
||||
policyNames, _, _ := models.RolePolicyManager.GetMatchPolicyGroup(&token, time.Time{}, true)
|
||||
token.Token.Policies.Project, _ = policyNames[rbacscope.ScopeProject]
|
||||
token.Token.Policies.Domain, _ = policyNames[rbacscope.ScopeDomain]
|
||||
token.Token.Policies.System, _ = policyNames[rbacscope.ScopeSystem]
|
||||
token.Token.Policies.Project = policyNames[rbacscope.ScopeProject]
|
||||
token.Token.Policies.Domain = policyNames[rbacscope.ScopeDomain]
|
||||
token.Token.Policies.System = policyNames[rbacscope.ScopeSystem]
|
||||
|
||||
endpoints, err := models.EndpointManager.FetchAll()
|
||||
if err != nil {
|
||||
@@ -368,6 +415,15 @@ func (t *SAuthToken) getTokenV3(
|
||||
token.Token.Catalog = endpoints.GetKeystoneCatalogV3()
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
err := models.TokenCacheManager.Save(ctx, token.Id, t.ExpiresAt, t.Method, t.AuditIds, t.UserId, t.ProjectId, t.DomainId, t.Context.Source, t.Context.Ip)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Save Token")
|
||||
}
|
||||
cache.Save(token.Id, &token)
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
@@ -383,7 +439,7 @@ func (t *SAuthToken) getTokenV2(
|
||||
token.User.IsSystemAccount = user.IsSystemAccount
|
||||
token.Context = t.Context
|
||||
|
||||
tk, err := t.EncodeFernetToken()
|
||||
tk, err := t.encodeShortToken()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "EncodeFernetToken")
|
||||
}
|
||||
@@ -435,5 +491,13 @@ func (t *SAuthToken) getTokenV2(
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
err := models.TokenCacheManager.Save(ctx, token.Token.Id, t.ExpiresAt, t.Method, t.AuditIds, t.UserId, t.ProjectId, t.DomainId, t.Context.Source, t.Context.Ip)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Save Token")
|
||||
}
|
||||
cache.Save(token.Token.Id, &token)
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@ import (
|
||||
)
|
||||
|
||||
func FernetTokenVerifier(ctx context.Context, tokenStr string) (mcclient.TokenCredential, error) {
|
||||
token := SAuthToken{}
|
||||
err := token.ParseFernetToken(tokenStr)
|
||||
token, err := TokenStrDecode(tokenStr)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewInvalidCredentialError("invalid token %s", err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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 stringutils2
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func GenId(ids ...string) string {
|
||||
h := sha256.New()
|
||||
for _, id := range ids {
|
||||
h.Write([]byte(id))
|
||||
}
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
Reference in New Issue
Block a user