| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- // 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, "%v", 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
- }
|