diff --git a/pkg/apigateway/handler/auth.go b/pkg/apigateway/handler/auth.go index d7b3dd03df..bf1b314d8b 100644 --- a/pkg/apigateway/handler/auth.go +++ b/pkg/apigateway/handler/auth.go @@ -671,7 +671,14 @@ func getUserInfo(ctx context.Context, s *mcclient.ClientSession, token mcclient. return nil, fmt.Errorf("not found user %s", token.GetUserId()) } data := jsonutils.NewDict() - for _, k := range []string{"displayname", "email", "id", "name", "enabled", "mobile", "allow_web_console", "created_at", "enable_mfa", "is_system_account", "last_active_at", "last_login_ip", "last_login_source", "idp_driver"} { + for _, k := range []string{ + "displayname", "email", "id", "name", + "enabled", "mobile", "allow_web_console", + "created_at", "enable_mfa", "is_system_account", + "last_active_at", "last_login_ip", + "last_login_source", "idp_driver", + "password_expires_at", "failed_auth_count", "failed_auth_at", + } { v, e := usr.Get(k) if e == nil { data.Add(v, k) diff --git a/pkg/apis/identity/usrext.go b/pkg/apis/identity/usrext.go index 5fffeeaf42..43bda3d67d 100644 --- a/pkg/apis/identity/usrext.go +++ b/pkg/apis/identity/usrext.go @@ -25,6 +25,8 @@ type SUserExtended struct { LastActiveAt time.Time DomainId string + IsSystemAccount bool + Displayname string Email string Mobile string diff --git a/pkg/keystone/driver/sql/sql.go b/pkg/keystone/driver/sql/sql.go index 8d8739038b..a62f718703 100644 --- a/pkg/keystone/driver/sql/sql.go +++ b/pkg/keystone/driver/sql/sql.go @@ -50,7 +50,7 @@ func (sql *SSQLDriver) Authenticate(ctx context.Context, ident mcclient.SAuthent if err != nil { return nil, errors.Wrap(err, "UserManager.FetchUserExtended") } - localUser, err := models.LocalUserManager.FetchLocalUser(usrExt) + localUser, err := models.LocalUserManager.FetchLocalUserById(usrExt.LocalId) if err != nil { return nil, errors.Wrap(err, "LocalUserManager.FetchLocalUser") } diff --git a/pkg/keystone/models/localusers.go b/pkg/keystone/models/localusers.go index 3b176960ab..dea6de28f5 100644 --- a/pkg/keystone/models/localusers.go +++ b/pkg/keystone/models/localusers.go @@ -19,9 +19,9 @@ import ( "fmt" "time" - "github.com/pkg/errors" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" - api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" ) @@ -75,47 +75,54 @@ func (user *SLocalUser) GetName() string { return user.Name } -func (manager *SLocalUserManager) FetchLocalUser(usrExt *api.SUserExtended) (*SLocalUser, error) { +func (manager *SLocalUserManager) FetchLocalUserById(localId int) (*SLocalUser, error) { + return manager.fetchLocalUser("", "", localId) +} + +func (manager *SLocalUserManager) fetchLocalUser(userId string, domainId string, localId int) (*SLocalUser, error) { localUser := SLocalUser{} localUser.SetModelManager(manager, &localUser) - q := manager.Query().Equals("id", usrExt.LocalId) + var q *sqlchemy.SQuery + if len(userId) > 0 && len(domainId) > 0 { + q = manager.Query().Equals("user_id", userId).Equals("domain_id", domainId) + } else { + q = manager.Query().Equals("id", localId) + } err := q.First(&localUser) if err != nil { - return nil, errors.Wrap(err, "Query") + if err == sql.ErrNoRows { + return nil, err + } else { + return nil, errors.Wrap(err, "Query") + } } return &localUser, nil } func (manager *SLocalUserManager) register(userId string, domainId string, name string) (*SLocalUser, error) { - localUser := SLocalUser{} - localUser.SetModelManager(manager, &localUser) - - q := manager.Query().Equals("user_id", userId).Equals("domain_id", domainId) - err := q.First(&localUser) + localUser, err := manager.fetchLocalUser(userId, domainId, 0) if err != nil && err != sql.ErrNoRows { return nil, errors.Wrap(err, "Query") } if err == nil { - return &localUser, nil + return localUser, nil } + + localUser = &SLocalUser{} localUser.UserId = userId localUser.DomainId = domainId localUser.Name = name - err = manager.TableSpec().Insert(&localUser) + err = manager.TableSpec().Insert(localUser) if err != nil { return nil, errors.Wrap(err, "Insert") } - return &localUser, nil + return localUser, nil } func (manager *SLocalUserManager) delete(userId string, domainId string) (*SLocalUser, error) { - localUser := SLocalUser{} - localUser.SetModelManager(manager, &localUser) - - q := manager.Query().Equals("user_id", userId).Equals("domain_id", domainId) - err := q.First(&localUser) + localUser, err := manager.fetchLocalUser(userId, domainId, 0) if err != nil && err != sql.ErrNoRows { return nil, errors.Wrap(err, "Query") } @@ -123,7 +130,7 @@ func (manager *SLocalUserManager) delete(userId string, domainId string) (*SLoca return nil, nil } - _, err = db.Update(&localUser, func() error { + _, err = db.Update(localUser, func() error { return localUser.MarkDelete() }) @@ -131,7 +138,7 @@ func (manager *SLocalUserManager) delete(userId string, domainId string) (*SLoca return nil, errors.Wrap(err, "MarkDelete") } - return &localUser, nil + return localUser, nil } func (usr *SLocalUser) SaveFailedAuth() error { diff --git a/pkg/keystone/models/passwords.go b/pkg/keystone/models/passwords.go index 273d9687dd..ae022cce9c 100644 --- a/pkg/keystone/models/passwords.go +++ b/pkg/keystone/models/passwords.go @@ -110,7 +110,7 @@ func (manager *SPasswordManager) fetchByLocaluserId(localUserId int) ([]SPasswor return passes, nil } -func (manager *SPasswordManager) verifyPassword(localUserId int, password string) error { +func (manager *SPasswordManager) validatePassword(localUserId int, password string) error { if o.Options.PasswordMinimalLength > 0 && len(password) < o.Options.PasswordMinimalLength { return errors.Error("too simple password") } @@ -129,7 +129,7 @@ func (manager *SPasswordManager) verifyPassword(localUserId int, password string return nil } -func (manager *SPasswordManager) savePassword(localUserId int, password string) error { +func (manager *SPasswordManager) savePassword(localUserId int, password string, isSystemAccount bool) error { hash, err := seclib2.BcryptPassword(password) if err != nil { return errors.Wrap(err, "seclib2.BcryptPassword") @@ -140,7 +140,7 @@ func (manager *SPasswordManager) savePassword(localUserId int, password string) rec.Password = shaPassword(password) now := time.Now() rec.CreatedAtInt = now.UnixNano() / 1000 - if o.Options.PasswordExpirationDays > 0 { + if o.Options.PasswordExpirationDays > 0 && !isSystemAccount { rec.ExpiresAt = now.Add(24 * time.Hour * time.Duration(o.Options.PasswordExpirationDays)) rec.ExpiresAtInt = rec.ExpiresAt.UnixNano() / 1000 } diff --git a/pkg/keystone/models/users.go b/pkg/keystone/models/users.go index a03a7d3f2c..183b8c3cd8 100644 --- a/pkg/keystone/models/users.go +++ b/pkg/keystone/models/users.go @@ -244,6 +244,7 @@ func (manager *SUserManager) FetchUserExtended(userId, userName, domainId, domai users.Field("created_at"), users.Field("last_active_at"), users.Field("domain_id"), + users.Field("is_system_account"), localUsers.Field("id", "local_id"), localUsers.Field("name", "local_name"), domains.Field("name", "domain_name"), @@ -300,13 +301,15 @@ func localUserVerifyPassword(user *api.SUserExtended, passwd string) error { if len(passes) == 0 { return nil } - //for i := range passes { + // password expiration check skip system account + if !passes[0].ExpiresAt.IsZero() && passes[0].ExpiresAt.Before(time.Now()) && !user.IsSystemAccount { + return errors.Error("password expires") + } err = seclib2.BcryptVerifyPassword(passwd, passes[0].PasswordHash) if err == nil { return nil } - //} - return fmt.Errorf("invalid password for %s", user.Name) + return errors.Error("invalid password") } func (manager *SUserManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { @@ -421,7 +424,7 @@ func (user *SUser) ValidateUpdateData(ctx context.Context, userCred mcclient.Tok if err != nil { return nil, errors.Wrap(err, "UserManager.FetchUserExtended") } - err = PasswordManager.verifyPassword(usrExt.LocalId, passwd) + err = PasswordManager.validatePassword(usrExt.LocalId, passwd) if err != nil { return nil, httperrors.NewInputParameterError("invalid password: %s", err) } @@ -483,6 +486,19 @@ func userExtra(user *SUser, extra *jsonutils.JSONDict) *jsonutils.JSONDict { extra.Add(jsonutils.NewInt(int64(prjCnt)), "project_count") credCnt, _ := user.GetCredentialCount() extra.Add(jsonutils.NewInt(int64(credCnt)), "credential_count") + + localUser, _ := LocalUserManager.fetchLocalUser(user.Id, user.DomainId, 0) + if localUser != nil { + if localUser.FailedAuthCount > 0 { + extra.Add(jsonutils.NewInt(int64(localUser.FailedAuthCount)), "failed_auth_count") + extra.Add(jsonutils.NewTimeString(localUser.FailedAuthAt), "failed_auth_at") + } + localPass, _ := PasswordManager.FetchLastPassword(localUser.Id) + if localPass != nil && !localPass.ExpiresAt.IsZero() { + extra.Add(jsonutils.NewTimeString(localPass.ExpiresAt), "password_expires_at") + } + } + return extra } @@ -492,7 +508,7 @@ func (user *SUser) initLocalData(passwd string) error { return errors.Wrap(err, "register localuser") } if len(passwd) > 0 { - err = PasswordManager.savePassword(localUsr.Id, passwd) + err = PasswordManager.savePassword(localUsr.Id, passwd, user.IsSystemAccount.Bool()) if err != nil { return errors.Wrap(err, "save password") } @@ -521,7 +537,7 @@ func (user *SUser) PostUpdate(ctx context.Context, userCred mcclient.TokenCreden log.Errorf("UserManager.FetchUserExtended fail %s", err) return } - err = PasswordManager.savePassword(usrExt.LocalId, passwd) + err = PasswordManager.savePassword(usrExt.LocalId, passwd, user.IsSystemAccount.Bool()) if err != nil { log.Errorf("fail to set password %s", err) return