Files
cloudpods/pkg/keystone/tokens/token.go
T

542 lines
16 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 tokens
import (
"context"
"database/sql"
"strings"
"time"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/rbacscope"
"yunion.io/x/pkg/utils"
api "yunion.io/x/onecloud/pkg/apis/identity"
"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 (
defaultAuthToken *SAuthToken
defaultAuthTokenStr string
)
func GetDefaultToken() string {
now := time.Now()
if defaultAuthToken == nil || defaultAuthToken.ExpiresAt.Sub(now) < time.Duration(3600) {
simpleToken := models.GetDefaultAdminCred()
defaultAuthToken = &SAuthToken{
UserId: simpleToken.GetUserId(),
Method: api.AUTH_METHOD_TOKEN,
ProjectId: simpleToken.GetProjectId(),
ExpiresAt: now.Add(time.Duration(options.Options.TokenExpirationSeconds) * time.Second),
AuditIds: []string{utils.GenRequestId(16)},
}
var err error
defaultAuthTokenStr, err = defaultAuthToken.encodeFernetToken()
if err != nil {
log.Fatalf("defaultAuthToken.EncodeFernetToken fail: %s", err)
}
if simpleToken.(*mcclient.SSimpleToken).Token != defaultAuthTokenStr {
simpleToken.(*mcclient.SSimpleToken).Token = defaultAuthTokenStr
}
}
return defaultAuthTokenStr
}
func GetDefaultAdminCredToken() mcclient.TokenCredential {
GetDefaultToken()
return models.GetDefaultAdminCred()
}
type SAuthToken struct {
UserId string
Method string
ProjectId string
DomainId string
ExpiresAt time.Time
AuditIds []string
Context mcclient.SAuthContext
}
func (t *SAuthToken) Decode(tk []byte) error {
for _, payload := range []ITokenPayload{
&SProjectScopedPayloadWithContext{},
&SDomainScopedPayloadWithContext{},
&SUnscopedPayloadWithContext{},
&SProjectScopedPayload{},
&SDomainScopedPayload{},
&SUnscopedPayload{},
} {
err := payload.Unmarshal(tk)
if err == nil {
payload.Decode(t)
return nil
}
}
return ErrInvalidFernetToken
}
func (t *SAuthToken) getProjectScopedPayload() ITokenPayload {
p := SProjectScopedPayload{}
p.Version = SProjectScopedPayloadVersion
p.UserId.parse(t.UserId)
p.ProjectId.parse(t.ProjectId)
p.Method = authMethodStr2Id(t.Method)
p.ExpiresAt = float64(t.ExpiresAt.Unix())
p.AuditIds = auditStrings2Bytes(t.AuditIds)
return &p
}
func (t *SAuthToken) getProjectScopedPayloadWithContext() ITokenPayload {
p := SProjectScopedPayloadWithContext{}
p.Version = SProjectScopedPayloadWithContextVersion
p.UserId.parse(t.UserId)
p.ProjectId.parse(t.ProjectId)
p.Method = authMethodStr2Id(t.Method)
p.ExpiresAt = float64(t.ExpiresAt.Unix())
p.AuditIds = auditStrings2Bytes(t.AuditIds)
p.Context = authContext2Payload(t.Context)
return &p
}
func (t *SAuthToken) getDomainScopedPayload() ITokenPayload {
p := SDomainScopedPayload{}
p.Version = SDomainScopedPayloadVersion
p.UserId.parse(t.UserId)
p.DomainId.parse(t.DomainId)
p.Method = authMethodStr2Id(t.Method)
p.ExpiresAt = float64(t.ExpiresAt.Unix())
p.AuditIds = auditStrings2Bytes(t.AuditIds)
return &p
}
func (t *SAuthToken) getDomainScopedPayloadWithContext() ITokenPayload {
p := SDomainScopedPayloadWithContext{}
p.Version = SDomainScopedPayloadVersion
p.UserId.parse(t.UserId)
p.DomainId.parse(t.DomainId)
p.Method = authMethodStr2Id(t.Method)
p.ExpiresAt = float64(t.ExpiresAt.Unix())
p.AuditIds = auditStrings2Bytes(t.AuditIds)
p.Context = authContext2Payload(t.Context)
return &p
}
func (t *SAuthToken) getUnscopedPayload() ITokenPayload {
p := SUnscopedPayload{}
p.Version = SUnscopedPayloadVersion
p.UserId.parse(t.UserId)
p.Method = authMethodStr2Id(t.Method)
p.ExpiresAt = float64(t.ExpiresAt.Unix())
p.AuditIds = auditStrings2Bytes(t.AuditIds)
return &p
}
func (t *SAuthToken) getUnscopedPayloadWithContext() ITokenPayload {
p := SUnscopedPayloadWithContext{}
p.Version = SUnscopedPayloadVersion
p.UserId.parse(t.UserId)
p.Method = authMethodStr2Id(t.Method)
p.ExpiresAt = float64(t.ExpiresAt.Unix())
p.AuditIds = auditStrings2Bytes(t.AuditIds)
p.Context = authContext2Payload(t.Context)
return &p
}
func (t *SAuthToken) getPayload() ITokenPayload {
if len(t.ProjectId) > 0 {
return t.getProjectScopedPayloadWithContext()
}
if len(t.DomainId) > 0 {
return t.getDomainScopedPayloadWithContext()
}
return t.getUnscopedPayloadWithContext()
}
func (t *SAuthToken) Encode() ([]byte, error) {
return t.getPayload().Encode()
}
func (t *SAuthToken) IsExpired() bool {
return t.ValidDuration() <= 0
}
func (t *SAuthToken) ValidDuration() time.Duration {
return time.Until(t.ExpiresAt)
}
func TokenStrDecode(ctx context.Context, tokenStr string) (*SAuthToken, error) {
var fernetToken string
if len(tokenStr) > api.AUTH_TOKEN_LENGTH {
// old style fernet token, convert to new token of sha256 hash of fernetToken
fernetToken = tokenStr
tokenStr = stringutils2.GenId(fernetToken)
}
token, err := models.TokenCacheManager.FetchToken(tokenStr)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
// not found
if len(fernetToken) > 0 {
// try to decode fernetToken
token := &SAuthToken{}
err := token.parseFernetToken(fernetToken)
if err != nil {
return nil, errors.Wrap(err, "parseFernetToken")
}
if token.IsExpired() {
return nil, ErrExpiredToken
}
// we should save the token for later use
{
err := models.TokenCacheManager.Save(ctx, tokenStr, token.ExpiresAt, token.Method, token.AuditIds,
token.UserId, token.ProjectId, token.DomainId, token.Context.Source, token.Context.Ip)
if err != nil {
log.Errorf("TokenStrDecode: save token fail %s", err)
}
}
return token, nil
} else {
// ??? not a fernetToken
return nil, errors.Wrapf(ErrTokenNotFound, "token %q not found", tokenStr)
}
} else {
return nil, errors.Wrap(err, "FetchToken")
}
} else {
if !token.Valid {
return nil, ErrInvalidToken
}
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(ErrInvalidFernetToken, tokenStr)
}
err := t.Decode(tk)
if err != nil {
return errors.Wrap(err, "decode error")
}
if t.ExpiresAt.Before(time.Now()) {
return errors.Wrapf(ErrExpiredToken, "expires_at %s", t.ExpiresAt.String())
}
return nil
}
func (t *SAuthToken) encodeFernetToken() (string, error) {
tk, err := t.Encode()
if err != nil {
return "", errors.Wrap(err, "encode error")
}
ftk, err := keys.TokenKeysManager.Encrypt(tk)
if err != nil {
return "", errors.Wrap(err, "TokenKeysManager.Encrypt")
}
return string(ftk), nil
}
func (t *SAuthToken) encodeShortToken() (string, error) {
tk, err := t.encodeFernetToken()
if err != nil {
return "", errors.Wrap(err, "encodeFernetToken")
}
stk := stringutils2.GenId(tk)
// log.Debugf("encodeShortToken %s => %s", tk, stk)
return stk, nil
}
func (t *SAuthToken) GetSimpleUserCred(token string) (mcclient.TokenCredential, error) {
userExt, err := models.UserManager.FetchUserExtended(t.UserId, "", "", "")
if err != nil {
return nil, errors.Wrap(err, "UserManager.FetchUserExtended")
}
ret := mcclient.SSimpleToken{
Token: token,
UserId: t.UserId,
User: userExt.Name,
Domain: userExt.DomainName,
DomainId: userExt.DomainId,
Expires: t.ExpiresAt,
Context: t.Context,
SystemAccount: userExt.IsSystemAccount,
}
var roles []models.SRole
if len(t.ProjectId) > 0 {
proj, err := models.ProjectManager.FetchProjectById(t.ProjectId)
if err != nil {
return nil, errors.Wrap(err, "ProjectManager.FetchProjectById")
}
ret.ProjectId = t.ProjectId
ret.Project = proj.Name
ret.ProjectDomainId = proj.DomainId
ret.ProjectDomain = proj.GetDomain().Name
roles, err = models.AssignmentManager.FetchUserProjectRoles(t.UserId, t.ProjectId)
} else if len(t.DomainId) > 0 {
domain, err := models.DomainManager.FetchDomainById(t.DomainId)
if err != nil {
return nil, errors.Wrap(err, "DomainManager.FetchDomainById")
}
ret.ProjectDomainId = t.DomainId
ret.ProjectDomain = domain.Name
roles, err = models.AssignmentManager.FetchUserProjectRoles(t.UserId, t.DomainId)
}
roleStrs := make([]string, len(roles))
roleIdStrs := make([]string, len(roles))
for i := range roles {
roleStrs[i] = roles[i].Name
roleIdStrs[i] = roles[i].Id
}
ret.Roles = strings.Join(roleStrs, ",")
ret.RoleIds = strings.Join(roleIdStrs, ",")
return &ret, nil
}
func (t *SAuthToken) getRoles() ([]models.SRole, error) {
var roleProjectId string
if len(t.ProjectId) > 0 {
roleProjectId = t.ProjectId
} else if len(t.DomainId) > 0 {
roleProjectId = t.DomainId
}
if len(roleProjectId) > 0 {
return models.AssignmentManager.FetchUserProjectRoles(t.UserId, roleProjectId)
}
return nil, nil
}
func (t *SAuthToken) getTokenV3(
ctx context.Context,
user *api.SUserExtended,
project *models.SProjectExtended,
domain *models.SDomain,
akskInfo api.SAccessKeySecretInfo,
) (*mcclient.TokenCredentialV3, error) {
token := mcclient.TokenCredentialV3{}
token.Token.AccessKey = akskInfo
token.Token.ExpiresAt = t.ExpiresAt
token.Token.IssuedAt = t.ExpiresAt.Add(-time.Duration(options.Options.TokenExpirationSeconds) * time.Second)
token.Token.AuditIds = t.AuditIds
token.Token.Methods = []string{t.Method}
token.Token.User.Id = user.Id
token.Token.User.Name = user.Name
token.Token.User.Domain.Id = user.DomainId
token.Token.User.Domain.Name = user.DomainName
lastPass, _ := models.PasswordManager.FetchLastPassword(user.LocalId)
if lastPass != nil && !lastPass.ExpiresAt.IsZero() {
token.Token.User.PasswordExpiresAt = lastPass.ExpiresAt
}
token.Token.User.Displayname = user.Displayname
token.Token.User.Email = user.Email
token.Token.User.Mobile = user.Mobile
token.Token.User.IsSystemAccount = user.IsSystemAccount
token.Token.Context = t.Context
tk, err := t.encodeShortToken()
if err != nil {
return nil, errors.Wrap(err, "EncodeFernetToken")
}
token.Id = tk
roles, err := t.getRoles()
if err != nil {
return nil, errors.Wrap(err, "getRoles")
}
if len(roles) == 0 {
if project != nil || domain != nil {
if project != nil {
return nil, errors.Wrapf(ErrUserNotInProject, "project %q", project.Name)
}
if domain != nil {
return nil, errors.Wrapf(ErrUserNotInProject, "domain %q", domain.Name)
}
}
/*extProjs, err := models.ProjectManager.FetchUserProjects(user.Id)
if err != nil {
return nil, errors.Wrap(err, "models.ProjectManager.FetchUserProjects")
}
token.Token.Projects = make([]mcclient.KeystoneProjectV3, len(extProjs))
for i := range extProjs {
token.Token.Projects[i].Id = extProjs[i].Id
token.Token.Projects[i].Name = extProjs[i].Name
token.Token.Projects[i].Domain.Id = extProjs[i].DomainId
token.Token.Projects[i].Domain.Name = extProjs[i].DomainName
}*/
assigns, _, err := models.AssignmentManager.FetchAll(user.Id, "", "", "", "", "",
nil, nil, nil, nil, nil, nil,
true, true, true, true, true, 0, 0)
if err != nil {
return nil, errors.Wrap(err, "models.AssignmentManager.FetchAll")
}
token.Token.RoleAssignments = assigns
token.Token.Projects = make([]mcclient.KeystoneProjectV3, 0)
projMap := make(map[string]bool)
for i := range assigns {
if _, ok := projMap[assigns[i].Scope.Project.Id]; ok {
continue
}
projMap[assigns[i].Scope.Project.Id] = true
p := mcclient.KeystoneProjectV3{
Id: assigns[i].Scope.Project.Id,
Name: assigns[i].Scope.Project.Name,
Domain: mcclient.KeystoneDomainV3{
Id: assigns[i].Scope.Project.Domain.Id,
Name: assigns[i].Scope.Project.Domain.Name,
},
}
token.Token.Projects = append(token.Token.Projects, p)
}
} else {
if project != nil {
token.Token.IsDomain = false
token.Token.Project.Id = project.Id
token.Token.Project.Name = project.Name
token.Token.Project.Domain.Id = project.DomainId
token.Token.Project.Domain.Name = project.DomainName
} else if domain != nil {
token.Token.IsDomain = true
token.Token.Project.Id = domain.Id
token.Token.Project.Name = domain.Name
}
token.Token.Roles = make([]mcclient.KeystoneRoleV3, len(roles))
for i := range roles {
token.Token.Roles[i].Id = roles[i].Id
token.Token.Roles[i].Name = roles[i].Name
}
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]
endpoints, err := models.EndpointManager.FetchAll()
if err != nil {
return nil, errors.Wrap(err, "EndpointManager.FetchAll")
}
if endpoints != nil {
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
}
func (t *SAuthToken) getTokenV2(
ctx context.Context,
user *api.SUserExtended,
project *models.SProjectExtended,
) (*mcclient.TokenCredentialV2, error) {
token := mcclient.TokenCredentialV2{}
token.User.Name = user.Name
token.User.Id = user.Id
token.User.Username = user.Name
token.User.IsSystemAccount = user.IsSystemAccount
token.Context = t.Context
tk, err := t.encodeShortToken()
if err != nil {
return nil, errors.Wrap(err, "EncodeFernetToken")
}
token.Token.Id = tk
token.Token.Expires = t.ExpiresAt
roles, err := t.getRoles()
if err != nil {
return nil, errors.Wrap(err, "getRoles")
}
if len(roles) == 0 {
if project != nil {
return nil, errors.Wrapf(ErrUserNotInProject, "project %q", project.Name)
}
extProjs, err := models.ProjectManager.FetchUserProjects(user.Id)
if err != nil {
return nil, errors.Wrap(err, "models.ProjectManager.FetchUserProjects")
}
token.Tenants = make([]mcclient.KeystoneTenantV2, len(extProjs))
for i := range extProjs {
token.Tenants[i].Id = extProjs[i].Id
token.Tenants[i].Name = extProjs[i].Name
token.Tenants[i].Domain.Id = extProjs[i].DomainId
token.Tenants[i].Domain.Name = extProjs[i].DomainName
token.Tenants[i].Enabled = true
}
} else {
token.Token.Tenant.Id = project.Id
token.Token.Tenant.Name = project.Name
// token.Token.Tenant.Enabled = project.Enabled.Bool()
token.Token.Tenant.Description = project.Description
token.Token.Tenant.Domain.Id = project.DomainId
token.Token.Tenant.Domain.Name = project.DomainName
token.User.Roles = make([]mcclient.KeystoneRoleV2, len(roles))
token.Metadata.Roles = make([]string, len(roles))
for i := range roles {
token.User.Roles[i].Name = roles[i].Name
token.User.Roles[i].Id = roles[i].Id
token.Metadata.Roles[i] = roles[i].Name
}
endpoints, err := models.EndpointManager.FetchAll()
if err != nil {
return nil, errors.Wrap(err, "EndpointManager.FetchAll")
}
if endpoints != nil {
token.ServiceCatalog = endpoints.GetKeystoneCatalogV2()
}
}
{
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
}