mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-08-31 01:35:56 +08:00
bfc17bc3c7
Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
661 lines
21 KiB
Go
661 lines
21 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"
|
|
"time"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
"yunion.io/x/pkg/util/s3auth"
|
|
"yunion.io/x/sqlchemy"
|
|
|
|
api "yunion.io/x/onecloud/pkg/apis/identity"
|
|
notify_api "yunion.io/x/onecloud/pkg/apis/notify"
|
|
"yunion.io/x/onecloud/pkg/httperrors"
|
|
"yunion.io/x/onecloud/pkg/keystone/driver"
|
|
"yunion.io/x/onecloud/pkg/keystone/models"
|
|
"yunion.io/x/onecloud/pkg/keystone/options"
|
|
"yunion.io/x/onecloud/pkg/keystone/saml"
|
|
"yunion.io/x/onecloud/pkg/mcclient"
|
|
notify_modules "yunion.io/x/onecloud/pkg/mcclient/modules/notify"
|
|
"yunion.io/x/onecloud/pkg/util/logclient"
|
|
)
|
|
|
|
func authUserByTokenV2(ctx context.Context, input mcclient.SAuthenticationInputV2) (*api.SUserExtended, error) {
|
|
return authUserByToken(ctx, input.Auth.Token.Id)
|
|
}
|
|
|
|
func authUserByTokenV3(ctx context.Context, input mcclient.SAuthenticationInputV3) (*api.SUserExtended, error) {
|
|
return authUserByToken(ctx, input.Auth.Identity.Token.Id)
|
|
}
|
|
|
|
func authUserByToken(ctx context.Context, tokenStr string) (*api.SUserExtended, error) {
|
|
token, err := TokenStrDecode(ctx, tokenStr)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "token.TokenStrDecode")
|
|
}
|
|
extUser, err := models.UserManager.FetchUserExtended(token.UserId, "", "", "")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "FetchUserExtended")
|
|
}
|
|
extUser.AuditIds = []string{tokenStr}
|
|
return extUser, nil
|
|
}
|
|
|
|
func authUserByPasswordV2(ctx context.Context, input mcclient.SAuthenticationInputV2) (*api.SUserExtended, error) {
|
|
ident := mcclient.SAuthenticationIdentity{}
|
|
ident.Methods = []string{api.AUTH_METHOD_PASSWORD}
|
|
ident.Password.User.Name = input.Auth.PasswordCredentials.Username
|
|
ident.Password.User.Password = input.Auth.PasswordCredentials.Password
|
|
ident.Password.User.Domain.Id = api.DEFAULT_DOMAIN_ID
|
|
return authUserByIdentity(ctx, ident, input.Auth.Context)
|
|
}
|
|
|
|
func authUserByIdentityV3(ctx context.Context, input mcclient.SAuthenticationInputV3) (*api.SUserExtended, error) {
|
|
return authUserByIdentity(ctx, input.Auth.Identity, input.Auth.Context)
|
|
}
|
|
|
|
func authUserByIdentity(ctx context.Context, ident mcclient.SAuthenticationIdentity, authCtx mcclient.SAuthContext) (*api.SUserExtended, error) {
|
|
usr, err := authUserByIdentityInternal(ctx, &ident)
|
|
if err != nil {
|
|
// log event
|
|
ident.Password.User.Password = "***"
|
|
log.Errorf("authenticate fail for %s reason: %s", jsonutils.Marshal(ident), err)
|
|
user := logclient.NewSimpleObject(ident.Password.User.Id, ident.Password.User.Name, "user")
|
|
token := GetDefaultAdminCredToken()
|
|
token.(*mcclient.SSimpleToken).Context = authCtx
|
|
logclient.AddActionLogWithContext(ctx, user, logclient.ACT_AUTHENTICATE, err, token, false)
|
|
}
|
|
return usr, err
|
|
}
|
|
|
|
func authUserByIdentityInternal(ctx context.Context, ident *mcclient.SAuthenticationIdentity) (*api.SUserExtended, error) {
|
|
var idpId string
|
|
|
|
if len(ident.Password.User.Name) == 0 && len(ident.Password.User.Id) == 0 && len(ident.Password.User.Domain.Id) == 0 && len(ident.Password.User.Domain.Name) == 0 {
|
|
return nil, ErrEmptyAuth
|
|
}
|
|
if len(ident.Password.User.Name) > 0 && len(ident.Password.User.Id) == 0 && len(ident.Password.User.Domain.Id) == 0 && len(ident.Password.User.Domain.Name) == 0 {
|
|
// no user domain specified, try to find user domain
|
|
q := models.UserManager.Query().Equals("name", ident.Password.User.Name)
|
|
usrCnt, err := q.CountWithError()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Query user by name")
|
|
}
|
|
if usrCnt > 1 {
|
|
log.Errorf("find %d user with name %s", usrCnt, ident.Password.User.Name)
|
|
return nil, sqlchemy.ErrDuplicateEntry
|
|
} else if usrCnt == 0 {
|
|
log.Errorf("find no user with name %s", ident.Password.User.Name)
|
|
return nil, httperrors.ErrUserNotFound
|
|
} else {
|
|
// userCnt == 1
|
|
usr := models.SUser{}
|
|
usr.SetModelManager(models.UserManager, &usr)
|
|
err := q.First(&usr)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Query user")
|
|
}
|
|
ident.Password.User.Domain.Id = usr.DomainId
|
|
ident.Password.User.Id = usr.Id
|
|
}
|
|
}
|
|
|
|
usrExt, err := models.UserManager.FetchUserExtended(ident.Password.User.Id, ident.Password.User.Name,
|
|
ident.Password.User.Domain.Id, ident.Password.User.Domain.Name)
|
|
if err != nil && errors.Cause(err) != httperrors.ErrUserNotFound {
|
|
return nil, errors.Wrap(err, "UserManager.FetchUserExtended")
|
|
}
|
|
|
|
if err != nil {
|
|
log.Errorf("no such user %s locally, query external IDP: %s", ident.Password.User.Name, err)
|
|
// no such user locally, query domain idp
|
|
domain, err := models.DomainManager.FetchDomain(ident.Password.User.Domain.Id, ident.Password.User.Domain.Name)
|
|
if err != nil {
|
|
if errors.Cause(err) != sql.ErrNoRows {
|
|
return nil, errors.Wrap(err, "DomainManager.FetchDomain")
|
|
} else {
|
|
return nil, errors.Wrapf(httperrors.ErrUserNotFound, "domain %s", ident.Password.User.Domain.Name)
|
|
}
|
|
}
|
|
mapping, err := models.IdmappingManager.FetchFirstEntity(domain.Id, api.IdMappingEntityDomain)
|
|
if err != nil {
|
|
if errors.Cause(err) != sql.ErrNoRows {
|
|
return nil, errors.Wrap(err, "IdmappingManager.FetchEntity")
|
|
} else {
|
|
return nil, errors.Wrapf(httperrors.ErrUserNotFound, "idp")
|
|
}
|
|
}
|
|
idpId = mapping.IdpId
|
|
} else {
|
|
// check enable
|
|
if !usrExt.Enabled {
|
|
if usrExt.IsLocal && usrExt.LocalFailedAuthCount > options.Options.PasswordErrorLockCount {
|
|
// user locked
|
|
return nil, httperrors.ErrUserLocked
|
|
}
|
|
// user disabled
|
|
return nil, httperrors.ErrUserDisabled
|
|
}
|
|
// user is enabled, check expired time
|
|
if !usrExt.ExpiredAt.IsZero() && usrExt.ExpiredAt.Before(time.Now()) {
|
|
return nil, httperrors.ErrUserExpired
|
|
}
|
|
// user exists, query user's idp
|
|
idps, err := models.IdentityProviderManager.FetchIdentityProvidersByUserId(usrExt.Id, api.PASSWORD_PROTECTED_IDPS)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "IdentityProviderManager.FetchIdentityProvidersByUserId")
|
|
}
|
|
if len(idps) == 0 {
|
|
idpId = api.DEFAULT_IDP_ID
|
|
} else if len(idps) == 1 {
|
|
idpId = idps[0].Id
|
|
} else {
|
|
return nil, sqlchemy.ErrDuplicateEntry
|
|
}
|
|
}
|
|
|
|
if len(idpId) == 0 {
|
|
idpId = api.DEFAULT_IDP_ID
|
|
}
|
|
idpObj, err := models.IdentityProviderManager.FetchById(idpId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "IdentityProviderManager.FetchById")
|
|
}
|
|
|
|
idp := idpObj.(*models.SIdentityProvider)
|
|
|
|
if idp.Enabled.IsFalse() {
|
|
return nil, errors.Wrap(httperrors.ErrInvalidIdpStatus, "idp disabled")
|
|
}
|
|
|
|
if idp.Status != api.IdentityDriverStatusConnected && idp.Status != api.IdentityDriverStatusDisconnected {
|
|
return nil, errors.Wrapf(httperrors.ErrInvalidIdpStatus, "invalid idp status %s", idp.Status)
|
|
}
|
|
|
|
conf, err := models.GetConfigs(idp, true, nil, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "GetConfig")
|
|
}
|
|
|
|
backend, err := driver.GetDriver(idp.Driver, idp.Id, idp.Name, idp.Template, idp.TargetDomainId, conf)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "driver.GetDriver")
|
|
}
|
|
|
|
usr, err := backend.Authenticate(ctx, *ident)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Authenticate")
|
|
}
|
|
|
|
if idp.Status == api.IdentityDriverStatusDisconnected {
|
|
idp.MarkConnected(ctx, models.GetDefaultAdminCred())
|
|
}
|
|
return usr, nil
|
|
}
|
|
|
|
func authUserByCASV3(ctx context.Context, input mcclient.SAuthenticationInputV3) (*api.SUserExtended, error) {
|
|
var idp *models.SIdentityProvider
|
|
var err error
|
|
if len(input.Auth.Identity.Id) > 0 {
|
|
idp, err = models.IdentityProviderManager.FetchIdentityProviderById(input.Auth.Identity.Id)
|
|
if err != nil {
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "idp %s not found", input.Auth.Identity.Id)
|
|
} else {
|
|
return nil, errors.Wrap(err, "FetchIdentityProviderById")
|
|
}
|
|
}
|
|
} else {
|
|
idps, err := models.IdentityProviderManager.FetchEnabledProviders(api.IdentityDriverCAS)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "models.fetchEnabledProviders")
|
|
}
|
|
if len(idps) == 0 {
|
|
return nil, errors.Error("No cas identity provider")
|
|
}
|
|
if len(idps) > 1 {
|
|
return nil, errors.Error("more than 1 cas identity providers?")
|
|
}
|
|
idp = &idps[0]
|
|
}
|
|
|
|
conf, err := models.GetConfigs(idp, true, nil, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "idp.GetConfig")
|
|
}
|
|
|
|
backend, err := driver.GetDriver(idp.Driver, idp.Id, idp.Name, idp.Template, idp.TargetDomainId, conf)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "driver.GetDriver")
|
|
}
|
|
|
|
usr, err := backend.Authenticate(ctx, input.Auth.Identity)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Authenticate")
|
|
}
|
|
|
|
if idp.Status == api.IdentityDriverStatusDisconnected {
|
|
idp.MarkConnected(ctx, models.GetDefaultAdminCred())
|
|
}
|
|
|
|
return usr, nil
|
|
}
|
|
|
|
func authUserBySAML(ctx context.Context, input mcclient.SAuthenticationInputV3) (*api.SUserExtended, error) {
|
|
if !saml.IsSAMLEnabled() {
|
|
return nil, errors.Wrap(httperrors.ErrNotSupported, "unsupported SAML backend")
|
|
}
|
|
|
|
idp, err := models.IdentityProviderManager.FetchIdentityProviderById(input.Auth.Identity.Id)
|
|
if err != nil {
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "idp %s not found", input.Auth.Identity.Id)
|
|
} else {
|
|
return nil, errors.Wrap(err, "FetchIdentityProviderById")
|
|
}
|
|
}
|
|
|
|
conf, err := models.GetConfigs(idp, true, nil, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "idp.GetConfig")
|
|
}
|
|
|
|
backend, err := driver.GetDriver(idp.Driver, idp.Id, idp.Name, idp.Template, idp.TargetDomainId, conf)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "driver.GetDriver")
|
|
}
|
|
|
|
usr, err := backend.Authenticate(ctx, input.Auth.Identity)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Authenticate")
|
|
}
|
|
|
|
if idp.Status == api.IdentityDriverStatusDisconnected {
|
|
idp.MarkConnected(ctx, models.GetDefaultAdminCred())
|
|
}
|
|
|
|
return usr, nil
|
|
}
|
|
|
|
func authUserByOIDC(ctx context.Context, input mcclient.SAuthenticationInputV3) (*api.SUserExtended, error) {
|
|
idp, err := models.IdentityProviderManager.FetchIdentityProviderById(input.Auth.Identity.Id)
|
|
if err != nil {
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "idp %s not found", input.Auth.Identity.Id)
|
|
} else {
|
|
return nil, errors.Wrap(err, "FetchIdentityProviderById")
|
|
}
|
|
}
|
|
|
|
conf, err := models.GetConfigs(idp, true, nil, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "idp.GetConfig")
|
|
}
|
|
|
|
backend, err := driver.GetDriver(idp.Driver, idp.Id, idp.Name, idp.Template, idp.TargetDomainId, conf)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "driver.GetDriver")
|
|
}
|
|
|
|
usr, err := backend.Authenticate(ctx, input.Auth.Identity)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Authenticate")
|
|
}
|
|
|
|
if idp.Status == api.IdentityDriverStatusDisconnected {
|
|
idp.MarkConnected(ctx, models.GetDefaultAdminCred())
|
|
}
|
|
|
|
return usr, nil
|
|
}
|
|
|
|
func authUserByOAuth2(ctx context.Context, input mcclient.SAuthenticationInputV3) (*api.SUserExtended, error) {
|
|
idp, err := models.IdentityProviderManager.FetchIdentityProviderById(input.Auth.Identity.Id)
|
|
if err != nil {
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "idp %s not found", input.Auth.Identity.Id)
|
|
} else {
|
|
return nil, errors.Wrap(err, "FetchIdentityProviderById")
|
|
}
|
|
}
|
|
|
|
conf, err := models.GetConfigs(idp, true, nil, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "idp.GetConfig")
|
|
}
|
|
|
|
backend, err := driver.GetDriver(idp.Driver, idp.Id, idp.Name, idp.Template, idp.TargetDomainId, conf)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "driver.GetDriver")
|
|
}
|
|
|
|
usr, err := backend.Authenticate(ctx, input.Auth.Identity)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Authenticate")
|
|
}
|
|
|
|
if idp.Status == api.IdentityDriverStatusDisconnected {
|
|
idp.MarkConnected(ctx, models.GetDefaultAdminCred())
|
|
}
|
|
|
|
return usr, nil
|
|
}
|
|
|
|
func authUserByAccessKeyV3(ctx context.Context, input mcclient.SAuthenticationInputV3) (*api.SUserExtended, string, api.SAccessKeySecretInfo, error) {
|
|
var aksk api.SAccessKeySecretInfo
|
|
|
|
akskRequest, err := s3auth.Decode(input.Auth.Identity.AccessKeyRequest)
|
|
if err != nil {
|
|
return nil, "", aksk, errors.Wrap(err, "s3auth.Decode")
|
|
}
|
|
keyId := akskRequest.GetAccessKey()
|
|
obj, err := models.CredentialManager.FetchById(keyId)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, "", aksk, ErrInvalidAccessKeyId
|
|
} else {
|
|
return nil, "", aksk, errors.Wrap(err, "CredentialManager.FetchById")
|
|
}
|
|
}
|
|
credential := obj.(*models.SCredential)
|
|
if !credential.Enabled.IsTrue() {
|
|
return nil, "", aksk, errors.Wrap(httperrors.ErrInvalidStatus, "Access Key disabled")
|
|
}
|
|
akBlob, err := credential.GetAccessKeySecret()
|
|
if err != nil {
|
|
return nil, "", aksk, errors.Wrap(err, "credential.GetAccessKeySecret")
|
|
}
|
|
if !akBlob.IsValid() {
|
|
return nil, "", aksk, ErrExpiredAccessKey
|
|
}
|
|
aksk.AccessKey = keyId
|
|
aksk.Secret = akBlob.Secret
|
|
aksk.Expire = akBlob.Expire
|
|
|
|
err = akskRequest.Verify(akBlob.Secret)
|
|
if err != nil {
|
|
return nil, "", aksk, errors.Wrap(err, "Verify")
|
|
}
|
|
usrExt, err := models.UserManager.FetchUserExtended(credential.UserId, "", "", "")
|
|
if err != nil {
|
|
return nil, "", aksk, errors.Wrap(err, "UserManager.FetchUserExtended")
|
|
}
|
|
|
|
usrExt.AuditIds = []string{keyId}
|
|
|
|
return usrExt, credential.ProjectId, aksk, nil
|
|
}
|
|
|
|
func authUserByVerify(ctx context.Context, input mcclient.SAuthenticationInputV3) (*api.SUserExtended, error) {
|
|
extUser, err := models.UserManager.FetchUserExtended(input.Auth.Identity.Verify.Uid, "", "", "")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "FetchUserExtended")
|
|
}
|
|
s, err := GetDefaulAdminSession(ctx, "")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "GetDefaultAdminSession")
|
|
}
|
|
verifyInput := notify_api.ReceiverVerifyInput{
|
|
ContactType: input.Auth.Identity.Verify.ContactType,
|
|
Token: input.Auth.Identity.Verify.VerifyCode,
|
|
}
|
|
_, err = notify_modules.NotifyReceiver.PerformAction(s, extUser.Id, "verify", jsonutils.Marshal(verifyInput))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Verify")
|
|
}
|
|
|
|
extUser.AuditIds = []string{input.Auth.Identity.Verify.Uid}
|
|
|
|
return extUser, nil
|
|
}
|
|
|
|
// +onecloud:swagger-gen-route-method=POST
|
|
// +onecloud:swagger-gen-route-path=/v3/auth/tokens
|
|
// +onecloud:swagger-gen-route-tag=authentication
|
|
// +onecloud:swagger-gen-param-body-index=1
|
|
// +onecloud:swagger-gen-resp-index=0
|
|
// +onecloud:swagger-gen-resp-header=X-Subject-Token
|
|
// +onecloud:swagger-gen-resp-header=验证成功的keystone V3 token
|
|
|
|
// keystone v3认证API
|
|
func AuthenticateV3(ctx context.Context, input mcclient.SAuthenticationInputV3) (*mcclient.TokenCredentialV3, error) {
|
|
var akskInfo api.SAccessKeySecretInfo
|
|
var user *api.SUserExtended
|
|
var err error
|
|
if len(input.Auth.Identity.Methods) != 1 {
|
|
return nil, ErrInvalidAuthMethod
|
|
}
|
|
method := input.Auth.Identity.Methods[0]
|
|
switch method {
|
|
case api.AUTH_METHOD_TOKEN:
|
|
// auth by token
|
|
user, err = authUserByTokenV3(ctx, input)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "authUserByTokenV3")
|
|
}
|
|
case api.AUTH_METHOD_AKSK:
|
|
// auth by aksk
|
|
user, input.Auth.Scope.Project.Id, akskInfo, err = authUserByAccessKeyV3(ctx, input)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "authUserByAccessKeyV3")
|
|
}
|
|
case api.AUTH_METHOD_CAS:
|
|
// auth by apereo CAS
|
|
user, err = authUserByCASV3(ctx, input)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "authUserByCASV3")
|
|
}
|
|
case api.AUTH_METHOD_SAML:
|
|
// auth by SAML 2.0 IDP, keystone acts as a SAML SP
|
|
user, err = authUserBySAML(ctx, input)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "authUserBySAML")
|
|
}
|
|
case api.AUTH_METHOD_OIDC:
|
|
// auth by OpenID Connect, keystone acts as an OpenID Connect client
|
|
user, err = authUserByOIDC(ctx, input)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "authUserByOIDC")
|
|
}
|
|
case api.AUTH_METHOD_OAuth2:
|
|
// auth by customized OAuth2.0 provider, keystone acts as an OAuth2.0 app
|
|
user, err = authUserByOAuth2(ctx, input)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "authUserByOAuth2")
|
|
}
|
|
case api.AUTH_METHOD_VERIFY:
|
|
user, err = authUserByVerify(ctx, input)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "authUserByVerify")
|
|
}
|
|
case api.AUTH_METHOD_ASSUME:
|
|
user, err = authUserByAssume(ctx, input)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "authUserByAssume")
|
|
}
|
|
default:
|
|
// auth by other methods, e.g. password , etc...
|
|
user, err = authUserByIdentityV3(ctx, input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// user not found
|
|
if user == nil {
|
|
return nil, ErrUserNotFound
|
|
}
|
|
// user is not enabled
|
|
if !user.Enabled {
|
|
return nil, ErrUserDisabled
|
|
}
|
|
// user is expired
|
|
if !user.ExpiredAt.IsZero() && user.ExpiredAt.Before(time.Now()) {
|
|
return nil, httperrors.ErrUserExpired
|
|
}
|
|
|
|
if !user.DomainEnabled {
|
|
return nil, ErrDomainDisabled
|
|
}
|
|
|
|
token := SAuthToken{}
|
|
token.UserId = user.Id
|
|
token.Method = method
|
|
token.AuditIds = user.AuditIds
|
|
now := time.Now().UTC()
|
|
token.ExpiresAt = now.Add(time.Duration(options.Options.TokenExpirationSeconds) * time.Second)
|
|
if !user.ExpiredAt.IsZero() && user.ExpiredAt.Before(token.ExpiresAt) {
|
|
token.ExpiresAt = user.ExpiredAt
|
|
}
|
|
token.Context = input.Auth.Context
|
|
|
|
if len(input.Auth.Scope.Project.Id) == 0 && len(input.Auth.Scope.Project.Name) == 0 && len(input.Auth.Scope.Domain.Id) == 0 && len(input.Auth.Scope.Domain.Name) == 0 {
|
|
// unscoped auth
|
|
return token.getTokenV3(ctx, user, nil, nil, akskInfo)
|
|
}
|
|
var projExt *models.SProjectExtended
|
|
var domain *models.SDomain
|
|
if len(input.Auth.Scope.Project.Id) > 0 || len(input.Auth.Scope.Project.Name) > 0 {
|
|
project, err := models.ProjectManager.FetchProject(
|
|
input.Auth.Scope.Project.Id,
|
|
input.Auth.Scope.Project.Name,
|
|
input.Auth.Scope.Project.Domain.Id,
|
|
input.Auth.Scope.Project.Domain.Name,
|
|
)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "ProjectManager.FetchProject")
|
|
}
|
|
// if project.Enabled.IsFalse() {
|
|
// return nil, ErrProjectDisabled
|
|
// }
|
|
projExt, err = project.FetchExtend()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "project.FetchExtend")
|
|
}
|
|
token.ProjectId = project.Id
|
|
} else {
|
|
domain, err = models.DomainManager.FetchDomain(input.Auth.Scope.Domain.Id,
|
|
input.Auth.Scope.Domain.Name)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "DomainManager.FetchDomain")
|
|
}
|
|
if domain.Enabled.IsFalse() {
|
|
return nil, ErrDomainDisabled
|
|
}
|
|
token.DomainId = domain.Id
|
|
}
|
|
tokenV3, err := token.getTokenV3(ctx, user, projExt, domain, akskInfo)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "getTokenV3")
|
|
}
|
|
|
|
return tokenV3, nil
|
|
}
|
|
|
|
type SAuthenticateV2ResponseBody struct {
|
|
Access mcclient.TokenCredentialV2 `json:"access"`
|
|
}
|
|
|
|
// +onecloud:swagger-gen-route-method=POST
|
|
// +onecloud:swagger-gen-route-path=/v2.0/tokens
|
|
// +onecloud:swagger-gen-route-tag=authentication
|
|
// +onecloud:swagger-gen-param-body-index=1
|
|
// +onecloud:swagger-gen-resp-index=0
|
|
|
|
// keystone v2 认证接口,通过用户名/密码或者 token 认证
|
|
func AuthenticateV2(ctx context.Context, input mcclient.SAuthenticationInputV2) (*SAuthenticateV2ResponseBody, error) {
|
|
token, err := _authenticateV2(ctx, input)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "_authenticateV2")
|
|
}
|
|
body := SAuthenticateV2ResponseBody{
|
|
Access: *token,
|
|
}
|
|
return &body, nil
|
|
}
|
|
|
|
func _authenticateV2(ctx context.Context, input mcclient.SAuthenticationInputV2) (*mcclient.TokenCredentialV2, error) {
|
|
var user *api.SUserExtended
|
|
var err error
|
|
var method string
|
|
if len(input.Auth.Token.Id) > 0 {
|
|
// auth by token
|
|
user, err = authUserByTokenV2(ctx, input)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "authUserByTokenV2")
|
|
}
|
|
method = api.AUTH_METHOD_TOKEN
|
|
} else {
|
|
// auth by password
|
|
user, err = authUserByPasswordV2(ctx, input)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "authUserByPasswordV2")
|
|
}
|
|
method = api.AUTH_METHOD_PASSWORD
|
|
}
|
|
// user not found
|
|
if user == nil {
|
|
return nil, ErrUserNotFound
|
|
}
|
|
// user is not enabled
|
|
if !user.Enabled {
|
|
return nil, ErrUserDisabled
|
|
}
|
|
|
|
if !user.DomainEnabled {
|
|
return nil, ErrDomainDisabled
|
|
}
|
|
|
|
token := SAuthToken{}
|
|
token.UserId = user.Id
|
|
token.Method = method
|
|
token.AuditIds = user.AuditIds
|
|
now := time.Now().UTC()
|
|
token.ExpiresAt = now.Add(time.Duration(options.Options.TokenExpirationSeconds) * time.Second)
|
|
token.Context = input.Auth.Context
|
|
|
|
if len(input.Auth.TenantId) == 0 && len(input.Auth.TenantName) == 0 {
|
|
// unscoped auth
|
|
return token.getTokenV2(ctx, user, nil)
|
|
}
|
|
project, err := models.ProjectManager.FetchProject(
|
|
input.Auth.TenantId,
|
|
input.Auth.TenantName,
|
|
api.DEFAULT_DOMAIN_ID, "")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "ProjectManager.FetchProject")
|
|
}
|
|
// if project.Enabled.IsFalse() {
|
|
// return nil, ErrProjectDisabled
|
|
// }
|
|
token.ProjectId = project.Id
|
|
projExt, err := project.FetchExtend()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "project.FetchExtend")
|
|
}
|
|
|
|
tokenV2, err := token.getTokenV2(ctx, user, projExt)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "getTokenV2")
|
|
}
|
|
|
|
return tokenV2, nil
|
|
}
|