| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649 |
- // 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 models
- import (
- "context"
- "database/sql"
- "fmt"
- "time"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/tristate"
- "yunion.io/x/pkg/util/rbacscope"
- "yunion.io/x/sqlchemy"
- "yunion.io/x/onecloud/pkg/apis"
- api "yunion.io/x/onecloud/pkg/apis/identity"
- "yunion.io/x/onecloud/pkg/cloudcommon/consts"
- "yunion.io/x/onecloud/pkg/cloudcommon/db"
- "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
- "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
- "yunion.io/x/onecloud/pkg/cloudcommon/policy"
- "yunion.io/x/onecloud/pkg/httperrors"
- "yunion.io/x/onecloud/pkg/keystone/options"
- o "yunion.io/x/onecloud/pkg/keystone/options"
- "yunion.io/x/onecloud/pkg/mcclient"
- "yunion.io/x/onecloud/pkg/util/logclient"
- "yunion.io/x/onecloud/pkg/util/seclib2"
- "yunion.io/x/onecloud/pkg/util/stringutils2"
- )
- type SUserManager struct {
- SEnabledIdentityBaseResourceManager
- db.SRecordChecksumResourceBaseManager
- }
- var UserManager *SUserManager
- func init() {
- UserManager = &SUserManager{
- SEnabledIdentityBaseResourceManager: NewEnabledIdentityBaseResourceManager(
- SUser{},
- "user",
- "user",
- "users",
- ),
- SRecordChecksumResourceBaseManager: *db.NewRecordChecksumResourceBaseManager(),
- }
- UserManager.SetVirtualObject(UserManager)
- db.InitManager(func() {
- UserManager.TableSpec().ColumnSpec("lang").SetDefault(options.Options.DefaultUserLanguage)
- })
- notifyclient.AddNotifyDBHookResources(UserManager.KeywordPlural())
- }
- /*
- +--------------------+-------------+------+-----+---------+-------+
- | Field | Type | Null | Key | Default | Extra |
- +--------------------+-------------+------+-----+---------+-------+
- | id | varchar(64) | NO | PRI | NULL | |
- | extra | text | YES | | NULL | |
- | enabled | tinyint(1) | YES | | NULL | |
- | default_project_id | varchar(64) | YES | MUL | NULL | |
- | created_at | datetime | YES | | NULL | |
- | last_active_at | date | YES | | NULL | |
- | domain_id | varchar(64) | NO | MUL | NULL | |
- +--------------------+-------------+------+-----+---------+-------+
- */
- type SUser struct {
- db.SRecordChecksumResourceBase
- SEnabledIdentityBaseResource
- // 用户邮箱
- Email string `width:"64" charset:"utf8" nullable:"true" index:"true" list:"domain" update:"domain" create:"domain_optional"`
- // 用户手机号
- Mobile string `width:"20" charset:"utf8" nullable:"true" index:"true" list:"domain" update:"domain" create:"domain_optional"`
- // 显示名称,用户登录后显示在右上角菜单入口
- Displayname string `with:"128" charset:"utf8" nullable:"true" list:"domain" update:"domain" create:"domain_optional"`
- // 上次登录时间
- // deprecated
- // swagger:ignore
- LastActiveAt time.Time `nullable:"true" list:"domain"`
- // 上次用户登录IP
- // deprecated
- // swagger:ignore
- LastLoginIp string `nullable:"true" list:"domain"`
- // 上次用户登录方式,可能值有:web(web控制台),cli(命令行climc),API(api)
- // deprecated
- // swagger:ignore
- LastLoginSource string `nullable:"true" list:"domain"`
- // 是否为系统账号,系统账号不会检查密码复杂度,默认不在列表显示
- IsSystemAccount tristate.TriState `default:"false" list:"domain" update:"admin" create:"admin_optional"`
- // deprecated
- DefaultProjectId string `width:"64" charset:"ascii" nullable:"true"`
- // 是否允许登录Web控制台,如果是用于API访问的用户,可禁用web控制台登录
- AllowWebConsole tristate.TriState `default:"true" list:"domain" update:"domain" create:"domain_optional"`
- // 是否开启MFA
- EnableMfa tristate.TriState `default:"false" list:"domain" update:"domain" create:"domain_optional"`
- // 用户的默认语言设置,默认是zh_CN
- Lang string `width:"8" charset:"ascii" nullable:"false" list:"domain" update:"domain" create:"domain_optional"`
- // 过期时间
- ExpiredAt time.Time `nullable:"true" list:"domain" update:"domain" create:"domain_optional"`
- }
- func (manager *SUserManager) GetContextManagers() [][]db.IModelManager {
- return [][]db.IModelManager{
- {GroupManager},
- {ProjectManager},
- }
- }
- func (manager *SUserManager) InitializeData() error {
- q := manager.Query().IsNullOrEmpty("name")
- users := make([]SUser, 0)
- err := db.FetchModelObjects(manager, q, &users)
- if err != nil {
- return errors.Wrap(err, "FetchModelObjects")
- }
- for i := range users {
- extUser, err := manager.FetchUserExtended(users[i].Id, "", "", "")
- if err != nil {
- return errors.Wrap(err, "FetchUserExtended")
- }
- name := extUser.LocalName
- if len(name) == 0 {
- name = extUser.DomainName
- }
- var desc, email, mobile, dispName string
- if users[i].Extra != nil {
- desc, _ = users[i].Extra.GetString("description")
- email, _ = users[i].Extra.GetString("email")
- mobile, _ = users[i].Extra.GetString("mobile")
- dispName, _ = users[i].Extra.GetString("displayname")
- }
- _, err = db.Update(&users[i], func() error {
- users[i].Name = name
- if len(email) > 0 {
- users[i].Email = email
- }
- if len(mobile) > 0 {
- users[i].Mobile = mobile
- }
- if len(dispName) > 0 {
- users[i].Displayname = dispName
- }
- if len(desc) > 0 {
- users[i].Description = desc
- }
- return nil
- })
- if err != nil {
- return errors.Wrap(err, "update")
- }
- }
- {
- err := manager.initSystemAccount()
- if err != nil {
- return errors.Wrap(err, "initSystemAccount")
- }
- }
- {
- err := manager.initSysUser(context.TODO())
- if err != nil {
- return errors.Wrap(err, "initSystemAccount")
- }
- }
- {
- err := manager.migrateUserLogin()
- if err != nil {
- return errors.Wrap(err, "migrateUserLogin")
- }
- }
- return nil
- }
- func (manager *SUserManager) migrateUserLogin() error {
- userLoginQ := UserLoginManager.Query("user_id").SubQuery()
- q := manager.Query().NotIn("id", userLoginQ)
- rows, err := q.Rows()
- if err != nil {
- return errors.Wrap(err, "query.Rows")
- }
- defer rows.Close()
- type SUserLoginExt struct {
- SUserLogin
- Id string
- }
- for rows.Next() {
- userLogin := SUserLoginExt{}
- err := q.Row2Struct(rows, &userLogin)
- if err != nil {
- return errors.Wrap(err, "row2struct")
- }
- userLogin.UserId = userLogin.Id
- userLogin.SUserLogin.SetModelManager(UserLoginManager, &userLogin.SUserLogin)
- err = UserLoginManager.TableSpec().Insert(context.Background(), &userLogin.SUserLogin)
- if err != nil {
- return errors.Wrap(err, "insert")
- }
- }
- sql := fmt.Sprintf("UPDATE `%s` SET last_active_at = NULL, last_login_ip = NULL, last_login_source = NULL WHERE last_active_at IS NOT NULL", manager.TableSpec().Name())
- _, err = manager.TableSpec().GetTableSpec().Database().Exec(sql)
- if err != nil {
- return errors.Wrap(err, "exec batch update")
- }
- return nil
- }
- func (manager *SUserManager) initSystemAccount() error {
- q := manager.Query().IsNotEmpty("default_project_id")
- users := make([]SUser, 0)
- err := db.FetchModelObjects(manager, q, &users)
- if err != nil {
- return errors.Wrap(err, "FetchModelObjects")
- }
- for i := range users {
- _, err = db.Update(&users[i], func() error {
- users[i].IsSystemAccount = tristate.True
- users[i].DefaultProjectId = ""
- return nil
- })
- if err != nil {
- return errors.Wrap(err, "update")
- }
- }
- return nil
- }
- func (manager *SUserManager) initSysUser(ctx context.Context) error {
- q := manager.Query().Equals("name", api.SystemAdminUser)
- q = q.Equals("domain_id", api.DEFAULT_DOMAIN_ID)
- cnt, err := q.CountWithError()
- if err != nil {
- return errors.Wrap(err, "query")
- }
- if cnt == 1 {
- // if ResetAdminUserPassword is true, reset sysadmin password
- if o.Options.ResetAdminUserPassword {
- usr := SUser{}
- usr.SetModelManager(manager, &usr)
- err = q.First(&usr)
- if err != nil {
- return errors.Wrap(err, "ResetAdminUserPassword Query user")
- }
- err = usr.initLocalData(o.Options.BootstrapAdminUserPassword, false)
- if err != nil {
- return errors.Wrap(err, "initLocalData")
- }
- }
- return nil
- }
- if cnt > 2 {
- // ???
- log.Fatalf("duplicate sysadmin account???")
- }
- // insert
- usr := SUser{}
- usr.Name = api.SystemAdminUser
- usr.DomainId = api.DEFAULT_DOMAIN_ID
- usr.Enabled = tristate.True
- usr.IsSystemAccount = tristate.True
- usr.AllowWebConsole = tristate.False
- usr.EnableMfa = tristate.False
- usr.Description = "Boostrap system default admin user"
- usr.SetModelManager(manager, &usr)
- err = manager.TableSpec().Insert(ctx, &usr)
- if err != nil {
- return errors.Wrap(err, "insert")
- }
- err = usr.initLocalData(o.Options.BootstrapAdminUserPassword, false)
- if err != nil {
- return errors.Wrap(err, "initLocalData")
- }
- return nil
- }
- func (manager *SUserManager) EnforceUserMfa(ctx context.Context) error {
- if options.Options.ForceEnableMfa != "all" {
- return nil
- }
- q := manager.Query().IsFalse("enable_mfa")
- users := make([]SUser, 0)
- err := db.FetchModelObjects(manager, q, &users)
- if err != nil {
- return errors.Wrap(err, "FetchModelObjects")
- }
- for i := range users {
- _, err := db.Update(&users[i], func() error {
- users[i].EnableMfa = tristate.True
- return nil
- })
- if err != nil {
- return errors.Wrap(err, "update enable mfa")
- }
- logclient.AddSimpleActionLog(&users[i], logclient.ACT_UPDATE, "force enable mfa", GetDefaultAdminCred(), true)
- }
- return nil
- }
- /*
- Fetch extended userinfo by Id or name + domainId or name + domainName
- */
- func (manager *SUserManager) FetchUserExtended(userId, userName, domainId, domainName string) (*api.SUserExtended, error) {
- if len(userId) == 0 && len(userName) == 0 {
- return nil, sqlchemy.ErrEmptyQuery
- }
- localUsers := LocalUserManager.Query().SubQuery()
- // nonlocalUsers := NonlocalUserManager.Query().SubQuery()
- users := UserManager.Query().SubQuery()
- domains := DomainManager.Query().SubQuery()
- // idmappings := IdmappingManager.Query().SubQuery()
- q := users.Query(
- users.Field("id"),
- users.Field("name"),
- users.Field("displayname"),
- users.Field("email"),
- users.Field("mobile"),
- users.Field("enabled"),
- users.Field("default_project_id"),
- users.Field("created_at"),
- users.Field("last_active_at"),
- users.Field("domain_id"),
- users.Field("is_system_account"),
- users.Field("expired_at"),
- localUsers.Field("id", "local_id"),
- localUsers.Field("name", "local_name"),
- localUsers.Field("failed_auth_count", "local_failed_auth_count"),
- domains.Field("name", "domain_name"),
- domains.Field("enabled", "domain_enabled"),
- // idmappings.Field("domain_id", "idp_id"),
- // idmappings.Field("local_id", "idp_name"),
- )
- q = q.Join(domains, sqlchemy.Equals(users.Field("domain_id"), domains.Field("id")))
- q = q.LeftJoin(localUsers, sqlchemy.Equals(localUsers.Field("user_id"), users.Field("id")))
- // q = q.LeftJoin(idmappings, sqlchemy.Equals(users.Field("id"), idmappings.Field("public_id")))
- if len(userId) > 0 {
- q = q.Filter(sqlchemy.Equals(users.Field("id"), userId))
- } else if len(userName) > 0 {
- q = q.Filter(sqlchemy.Equals(users.Field("name"), userName))
- if len(domainId) == 0 && len(domainName) == 0 {
- domainId = api.DEFAULT_DOMAIN_ID
- }
- if len(domainId) > 0 {
- q = q.Filter(sqlchemy.Equals(domains.Field("id"), domainId))
- } else if len(domainName) > 0 {
- q = q.Filter(sqlchemy.Equals(domains.Field("name"), domainName))
- }
- }
- extUser := api.SUserExtended{}
- err := q.First(&extUser)
- if err != nil {
- if err == sql.ErrNoRows {
- return nil, httperrors.ErrUserNotFound
- }
- return nil, errors.Wrap(err, "query")
- }
- if extUser.LocalId > 0 {
- extUser.IsLocal = true
- }
- return &extUser, nil
- }
- func VerifyPassword(user *api.SUserExtended, passwd string) error {
- return localUserVerifyPassword(user, passwd)
- }
- func localUserVerifyPassword(user *api.SUserExtended, passwd string) error {
- pass, err := PasswordManager.FetchByLocaluserIdNewestPassword(user.LocalId)
- if err != nil {
- return errors.Wrap(err, "fetchPassword")
- }
- if pass == nil {
- return errors.Error("no valid password")
- }
- // password expiration check skip system account
- // if passes[0].IsExpired() && !user.IsSystemAccount {
- // return errors.Error("password expires")
- // }
- // password expires, no error returns but set user need to reset password silently
- if pass.IsExpired() {
- localUsr, err := LocalUserManager.fetchLocalUser("", "", user.LocalId)
- if err != nil {
- return errors.Wrap(err, "fetchLocalUser")
- }
- localUsr.markNeedResetPassword(true, api.PasswordResetHintExpire)
- }
- err = seclib2.BcryptVerifyPassword(passwd, pass.PasswordHash)
- if err == nil {
- return nil
- }
- return httperrors.ErrWrongPassword
- }
- // 用户列表
- func (manager *SUserManager) ListItemFilter(
- ctx context.Context,
- q *sqlchemy.SQuery,
- userCred mcclient.TokenCredential,
- query api.UserListInput,
- ) (*sqlchemy.SQuery, error) {
- q, err := manager.SEnabledIdentityBaseResourceManager.ListItemFilter(ctx, q, userCred, query.EnabledIdentityBaseResourceListInput)
- if err != nil {
- return nil, errors.Wrap(err, "SEnabledIdentityBaseResourceManager.ListItemFilter")
- }
- if len(query.Email) > 0 {
- q = q.In("email", query.Email)
- }
- if len(query.Mobile) > 0 {
- q = q.In("mobile", query.Mobile)
- }
- if len(query.Displayname) > 0 {
- q = q.In("displayname", query.Displayname)
- }
- if query.AllowWebConsole != nil {
- if *query.AllowWebConsole {
- q = q.IsTrue("allow_web_console")
- } else {
- q = q.IsFalse("allow_web_console")
- }
- }
- if query.EnableMfa != nil {
- if *query.EnableMfa {
- q = q.IsTrue("enable_mfa")
- } else {
- q = q.IsFalse("enable_mfa")
- }
- }
- groupStr := query.GroupId
- if len(groupStr) > 0 {
- groupObj, err := GroupManager.FetchByIdOrName(ctx, userCred, groupStr)
- if err != nil {
- if err == sql.ErrNoRows {
- return nil, httperrors.NewResourceNotFoundError2(GroupManager.Keyword(), groupStr)
- } else {
- return nil, httperrors.NewGeneralError(err)
- }
- }
- subq := UsergroupManager.Query("user_id").Equals("group_id", groupObj.GetId())
- q = q.In("id", subq.SubQuery())
- }
- projectStr := query.ProjectId
- if len(projectStr) > 0 {
- project, err := ProjectManager.FetchByIdOrName(ctx, userCred, projectStr)
- if err != nil {
- if err == sql.ErrNoRows {
- return nil, httperrors.NewResourceNotFoundError2(ProjectManager.Keyword(), projectStr)
- } else {
- return nil, httperrors.NewGeneralError(err)
- }
- }
- subq := AssignmentManager.fetchProjectUserIdsQuery(project.GetId())
- q = q.In("id", subq.SubQuery())
- }
- roleStr := query.RoleId
- if len(roleStr) > 0 {
- role, err := RoleManager.FetchByIdOrName(ctx, userCred, roleStr)
- if err != nil {
- if err == sql.ErrNoRows {
- return nil, httperrors.NewResourceNotFoundError2(RoleManager.Keyword(), roleStr)
- } else {
- return nil, httperrors.NewGeneralError(err)
- }
- }
- subq := AssignmentManager.Query("actor_id").Equals("role_id", role.GetId()).Equals("type", api.AssignmentUserProject).Distinct()
- if len(query.RoleAssignmentDomainId) > 0 {
- domain, err := DomainManager.FetchByIdOrName(ctx, userCred, query.RoleAssignmentDomainId)
- if err != nil {
- if err == sql.ErrNoRows {
- return nil, httperrors.NewResourceNotFoundError2(DomainManager.Keyword(), query.RoleAssignmentDomainId)
- } else {
- return nil, httperrors.NewGeneralError(err)
- }
- }
- projects := ProjectManager.Query("id").Equals("domain_id", domain.GetId()).SubQuery()
- subq = subq.In("target_id", projects.Query())
- }
- if len(query.RoleAssignmentProjectId) > 0 {
- project, err := ProjectManager.FetchByIdOrName(ctx, userCred, query.RoleAssignmentProjectId)
- if err != nil {
- if err == sql.ErrNoRows {
- return nil, httperrors.NewResourceNotFoundError2(ProjectManager.Keyword(), query.RoleAssignmentProjectId)
- } else {
- return nil, httperrors.NewGeneralError(err)
- }
- }
- subq = subq.Equals("target_id", project.GetId())
- }
- q = q.In("id", subq.SubQuery().Query())
- }
- if len(query.IdpId) > 0 {
- idpObj, err := IdentityProviderManager.FetchByIdOrName(ctx, userCred, query.IdpId)
- if err != nil {
- if errors.Cause(err) == sql.ErrNoRows {
- return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", IdentityProviderManager.Keyword(), query.IdpId)
- } else {
- return nil, errors.Wrap(err, "IdentityProviderManager.FetchByIdOrName")
- }
- }
- subq := IdmappingManager.FetchPublicIdsExcludesQuery(idpObj.GetId(), api.IdMappingEntityUser, nil)
- q = q.In("id", subq.SubQuery())
- }
- if len(query.IdpEntityId) > 0 {
- subq := IdmappingManager.Query("public_id").Equals("local_id", query.IdpEntityId).Equals("entity_type", api.IdMappingEntityUser)
- q = q.Equals("id", subq.SubQuery())
- }
- return q, nil
- }
- func (manager *SUserManager) OrderByExtraFields(
- ctx context.Context,
- q *sqlchemy.SQuery,
- userCred mcclient.TokenCredential,
- query api.UserListInput,
- ) (*sqlchemy.SQuery, error) {
- var err error
- q, err = manager.SEnabledIdentityBaseResourceManager.OrderByExtraFields(ctx, q, userCred, query.EnabledIdentityBaseResourceListInput)
- if err != nil {
- return nil, errors.Wrap(err, "SEnabledIdentityBaseResourceManager.OrderByExtraFields")
- }
- return q, nil
- }
- func (manager *SUserManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
- var err error
- q, err = manager.SEnabledIdentityBaseResourceManager.QueryDistinctExtraField(q, field)
- if err == nil {
- return q, nil
- }
- return q, httperrors.ErrNotFound
- }
- func (manager *SUserManager) FilterByHiddenSystemAttributes(q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
- q = manager.SEnabledIdentityBaseResourceManager.FilterByHiddenSystemAttributes(q, userCred, query, scope)
- isSystem := jsonutils.QueryBoolean(query, "system", false)
- if isSystem {
- var isAllow bool
- allowScope, _ := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), manager.KeywordPlural(), policy.PolicyActionList, "system")
- if !scope.HigherThan(allowScope) {
- isAllow = true
- }
- if !isAllow {
- isSystem = false
- }
- }
- if !isSystem {
- q = q.Filter(sqlchemy.OR(sqlchemy.IsNull(q.Field("is_system_account")), sqlchemy.IsFalse(q.Field("is_system_account"))))
- }
- return q
- }
- func (manager *SUserManager) ValidateCreateData(
- ctx context.Context,
- userCred mcclient.TokenCredential,
- ownerId mcclient.IIdentityProvider,
- query jsonutils.JSONObject,
- input api.UserCreateInput,
- ) (api.UserCreateInput, error) {
- var err error
- if len(input.Password) > 0 && (input.SkipPasswordComplexityCheck == nil || !*input.SkipPasswordComplexityCheck) {
- err = validatePasswordComplexity(input.Password)
- if err != nil {
- return input, errors.Wrap(err, "validatePasswordComplexity")
- }
- }
- input.EnabledIdentityBaseResourceCreateInput, err = manager.SEnabledIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EnabledIdentityBaseResourceCreateInput)
- if err != nil {
- return input, errors.Wrap(err, "SEnabledIdentityBaseResourceManager.ValidateCreateData")
- }
- if len(input.IdpId) > 0 {
- _, err := IdentityProviderManager.FetchIdentityProviderById(input.IdpId)
- if err != nil {
- if errors.Cause(err) == sql.ErrNoRows {
- return input, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", IdentityProviderManager.Keyword(), input.IdpId)
- } else {
- return input, errors.Wrap(err, "IdentityProviderManager.FetchIdentityProviderById")
- }
- }
- }
- quota := SIdentityQuota{
- SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()},
- User: 1,
- }
- err = quotas.CheckSetPendingQuota(ctx, userCred, "a)
- if err != nil {
- return input, errors.Wrapf(err, "CheckSetPendingQuota")
- }
- if options.Options.ForceEnableMfa != "disable" {
- boolTrue := true
- input.EnableMfa = &boolTrue
- }
- return input, nil
- }
- func (user *SUser) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.UserUpdateInput) (api.UserUpdateInput, error) {
- if len(input.Name) > 0 {
- if user.IsAdminUser() {
- return input, httperrors.NewForbiddenError("cannot alter sysadmin user name")
- }
- }
- if !user.IsLocal() {
- data := jsonutils.Marshal(input)
- for _, k := range []string{
- "name",
- // "displayname",
- // "email",
- // "mobile",
- "password",
- } {
- if data.Contains(k) {
- return input, httperrors.NewForbiddenError("field %s is readonly", k)
- }
- }
- }
- if len(input.Password) > 0 && (input.SkipPasswordComplexityCheck == nil || *input.SkipPasswordComplexityCheck == false) {
- passwd := input.Password
- usrExt, err := UserManager.FetchUserExtended(user.Id, "", "", "")
- if err != nil {
- return input, errors.Wrap(err, "UserManager.FetchUserExtended")
- }
- if !usrExt.IsLocal {
- return input, errors.Wrap(httperrors.ErrForbidden, "cannot update password for non-local user")
- }
- skipHistoryCheck := false
- if user.IsSystemAccount.Bool() {
- skipHistoryCheck = true
- }
- err = PasswordManager.validatePassword(usrExt.LocalId, passwd, skipHistoryCheck)
- if err != nil {
- return input, httperrors.NewInputParameterError("invalid password: %s", err)
- }
- }
- if options.Options.ForceEnableMfa != "disable" {
- boolTrue := true
- input.EnableMfa = &boolTrue
- }
- if input.ClearExpire != nil && *input.ClearExpire {
- tmZero := time.Time{}
- input.ExpiredAt = &tmZero
- }
- var err error
- input.EnabledIdentityBaseUpdateInput, err = user.SEnabledIdentityBaseResource.ValidateUpdateData(ctx, userCred, query, input.EnabledIdentityBaseUpdateInput)
- if err != nil {
- return input, errors.Wrap(err, "SEnabledIdentityBaseResource.ValidateUpdateData")
- }
- return input, nil
- }
- func (user *SUser) ValidateUpdateCondition(ctx context.Context) error {
- // if user.IsReadOnly() {
- // return httperrors.NewForbiddenError("readonly")
- // }
- return user.SEnabledIdentityBaseResource.ValidateUpdateCondition(ctx)
- }
- func (manager *SUserManager) fetchUserById(uid string) (*SUser, error) {
- obj, err := manager.FetchById(uid)
- if err != nil {
- return nil, errors.Wrap(err, "manager.FetchById")
- }
- return obj.(*SUser), nil
- }
- func (user *SUser) IsAdminUser() bool {
- return user.Name == api.SystemAdminUser && user.DomainId == api.DEFAULT_DOMAIN_ID
- }
- func (user *SUser) GetGroupCount() (int, error) {
- q := UsergroupManager.Query().Equals("user_id", user.Id)
- return q.CountWithError()
- }
- func (user *SUser) GetProjectCount() (int, error) {
- q := AssignmentManager.fetchUserProjectIdsQuery(user.Id)
- return q.CountWithError()
- }
- func (user *SUser) GetCredentialCount() (int, error) {
- q := CredentialManager.Query().Equals("user_id", user.Id)
- return q.CountWithError()
- }
- func (manager *SUserManager) FetchScopeResources(userIds []string) (map[string]api.ExternalResourceInfo, error) {
- resources := ScopeResourceManager.Query().In("owner_id", userIds).SubQuery()
- q := resources.Query(
- resources.Field("resource"),
- resources.Field("owner_id"),
- sqlchemy.SUM("res_count", resources.Field("count")),
- sqlchemy.MAX("last_update", resources.Field("updated_at")),
- )
- q = q.GroupBy(resources.Field("resource"))
- ret := []struct {
- Resource string
- OwnerId string
- ResCount int
- LastUpdate time.Time
- }{}
- err := q.All(&ret)
- if err != nil {
- return nil, err
- }
- result := map[string]api.ExternalResourceInfo{}
- for _, res := range ret {
- if res.ResCount <= 0 {
- continue
- }
- _, ok := result[res.OwnerId]
- if ok {
- result[res.OwnerId].ExtResource[res.Resource] = res.ResCount
- } else {
- result[res.OwnerId] = api.ExternalResourceInfo{
- ExtResource: map[string]int{
- res.Resource: res.ResCount,
- },
- ExtResourcesLastUpdate: res.LastUpdate,
- ExtResourcesNextUpdate: res.LastUpdate.Add(time.Duration(options.Options.FetchScopeResourceCountIntervalSeconds) * time.Second),
- }
- }
- }
- return result, nil
- }
- func (manager *SUserManager) FetchCustomizeColumns(
- ctx context.Context,
- userCred mcclient.TokenCredential,
- query jsonutils.JSONObject,
- objs []interface{},
- fields stringutils2.SSortedStrings,
- isList bool,
- ) []api.UserDetails {
- rows := make([]api.UserDetails, len(objs))
- identRows := manager.SEnabledIdentityBaseResourceManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
- userIds := make([]string, len(rows))
- for i := range rows {
- rows[i] = api.UserDetails{
- EnabledIdentityBaseResourceDetails: identRows[i],
- }
- userIds[i] = objs[i].(*SUser).Id
- }
- scopeResources, err := manager.FetchScopeResources(userIds)
- if err != nil {
- log.Errorf("FetchScopeResources error: %v", err)
- return rows
- }
- usage, err := manager.TotalResourceCount(userIds)
- if err != nil {
- log.Errorf("TotalResourceCount error: %v", err)
- return rows
- }
- projects := ProjectManager.Query().SubQuery()
- domains := DomainManager.Query().SubQuery()
- subq := manager.fetchProjectUnion(userIds)
- q := projects.Query(
- projects.Field("id"),
- projects.Field("name"),
- projects.Field("domain_id"),
- domains.Field("name").Label("domain_name"),
- subq.Field("actor_id").Label("user_id"),
- )
- q = q.Join(domains, sqlchemy.Equals(projects.Field("domain_id"), domains.Field("id")))
- q = q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("target_id")))
- userProjects := []struct {
- api.SFetchDomainObjectWithMetadata
- UserId string
- }{}
- err = q.All(&userProjects)
- if err != nil {
- log.Errorf("query projects error: %v", err)
- return rows
- }
- metaMap := map[string]map[string]string{}
- if db.IsAllowList(rbacscope.ScopeProject, userCred, db.Metadata).Result.IsAllow() {
- projectIds := []string{}
- for _, p := range userProjects {
- projectIds = append(projectIds, p.Id)
- }
- metadata := []db.SMetadata{}
- q = db.Metadata.Query().Equals("obj_type", ProjectManager.Keyword()).In("obj_id", projectIds)
- err = q.Filter(sqlchemy.NOT(sqlchemy.Startswith(q.Field("key"), db.SYSTEM_ADMIN_PREFIX))).All(&metadata)
- if err != nil {
- log.Errorf("query metdata error: %v", err)
- return rows
- }
- for _, meta := range metadata {
- _, ok := metaMap[meta.ObjId]
- if !ok {
- metaMap[meta.ObjId] = map[string]string{}
- }
- metaMap[meta.ObjId][meta.Key] = meta.Value
- }
- }
- projectMap := map[string][]api.SFetchDomainObjectWithMetadata{}
- for _, p := range userProjects {
- _, ok := projectMap[p.UserId]
- if !ok {
- projectMap[p.UserId] = []api.SFetchDomainObjectWithMetadata{}
- }
- p.SFetchDomainObjectWithMetadata.Metadata = metaMap[p.Id]
- projectMap[p.UserId] = append(projectMap[p.UserId], p.SFetchDomainObjectWithMetadata)
- }
- groups := GroupManager.Query().SubQuery()
- domains = DomainManager.Query().SubQuery()
- ugs := UsergroupManager.Query().In("user_id", userIds).SubQuery()
- q = groups.Query(
- groups.Field("id"),
- groups.Field("name"),
- domains.Field("id").Label("domain_id"),
- domains.Field("name").Label("domain"),
- ugs.Field("user_id"),
- )
- q = q.Join(domains, sqlchemy.Equals(groups.Field("domain_id"), domains.Field("id")))
- q = q.Join(ugs, sqlchemy.Equals(groups.Field("id"), ugs.Field("group_id")))
- useGroups := []struct {
- api.SUserGroup
- UserId string
- }{}
- err = q.All(&useGroups)
- if err != nil {
- log.Errorf("query user groups error: %v", err)
- return rows
- }
- groupMap := map[string][]api.SUserGroup{}
- for _, ug := range useGroups {
- _, ok := groupMap[ug.UserId]
- if !ok {
- groupMap[ug.UserId] = []api.SUserGroup{}
- }
- groupMap[ug.UserId] = append(groupMap[ug.UserId], ug.SUserGroup)
- }
- userLogins := make(map[string]*SUserLogin)
- userLoginRows := []SUserLogin{}
- err = UserLoginManager.Query().In("user_id", userIds).All(&userLoginRows)
- if err != nil {
- log.Errorf("query user logins error: %v", err)
- return rows
- }
- for _, userLogin := range userLoginRows {
- userLogins[userLogin.UserId] = &userLogin
- }
- for i := range rows {
- rows[i].ExternalResourceInfo = scopeResources[userIds[i]]
- rows[i].UserUsage = usage[userIds[i]]
- rows[i].Projects = projectMap[userIds[i]]
- rows[i].Groups = groupMap[userIds[i]]
- if userLogin, ok := userLogins[userIds[i]]; ok {
- rows[i].LastActiveAt = userLogin.LastActiveAt
- rows[i].LastLoginIp = userLogin.LastLoginIp
- rows[i].LastLoginSource = userLogin.LastLoginSource
- }
- }
- return rows
- }
- type SUserUsageCount struct {
- Id string
- api.UserUsage
- }
- func (m *SUserManager) query(manager db.IModelManager, field string, userIds []string, filter func(*sqlchemy.SQuery) *sqlchemy.SQuery) *sqlchemy.SSubQuery {
- q := manager.Query()
- if filter != nil {
- q = filter(q)
- }
- key := "user_id"
- sq := q.SubQuery()
- return sq.Query(
- sq.Field(key),
- sqlchemy.COUNT(field),
- ).In(key, userIds).GroupBy(sq.Field(key)).SubQuery()
- }
- func (manager *SUserManager) fetchProjectUnion(userIds []string) *sqlchemy.SUnion {
- p1 := AssignmentManager.Query("actor_id", "target_id", "type").Equals("type", api.AssignmentUserProject).IsFalse("inherited").In("actor_id", userIds)
- p2Q := UsergroupManager.Query().In("user_id", userIds).SubQuery()
- asq := AssignmentManager.Query().IsFalse("inherited").Equals("type", api.AssignmentGroupProject).SubQuery()
- p2 := p2Q.Query(
- p2Q.Field("user_id").Label("actor_id"),
- asq.Field("target_id").Label("target_id"),
- asq.Field("type").Label("type"),
- ).Join(asq, sqlchemy.Equals(p2Q.Field("group_id"), asq.Field("actor_id")))
- return sqlchemy.Union(p1, p2)
- }
- func (manager *SUserManager) TotalResourceCount(userIds []string) (map[string]api.UserUsage, error) {
- // group
- groupSQ := manager.query(UsergroupManager, "group_cnt", userIds, nil)
- // credential
- credSQ := manager.query(CredentialManager, "cred_cnt", userIds, nil)
- // project
- sq := manager.fetchProjectUnion(userIds)
- projectSQ := sq.Query(
- sq.Field("actor_id"),
- sqlchemy.COUNT("project_cnt"),
- ).GroupBy(sq.Field("actor_id")).SubQuery()
- users := manager.Query().SubQuery()
- userQ := users.Query(
- sqlchemy.SUM("group_count", groupSQ.Field("group_cnt")),
- sqlchemy.SUM("credential_count", credSQ.Field("cred_cnt")),
- sqlchemy.SUM("project_count", projectSQ.Field("project_cnt")),
- )
- userQ.AppendField(userQ.Field("id"))
- userQ = userQ.LeftJoin(groupSQ, sqlchemy.Equals(userQ.Field("id"), groupSQ.Field("user_id")))
- userQ = userQ.LeftJoin(credSQ, sqlchemy.Equals(userQ.Field("id"), credSQ.Field("user_id")))
- userQ = userQ.LeftJoin(projectSQ, sqlchemy.Equals(userQ.Field("id"), projectSQ.Field("actor_id")))
- userQ = userQ.Filter(sqlchemy.In(userQ.Field("id"), userIds)).GroupBy(userQ.Field("id"))
- userCount := []SUserUsageCount{}
- err := userQ.All(&userCount)
- if err != nil {
- return nil, errors.Wrapf(err, "userQ.All")
- }
- localUsers := []SLocalUser{}
- err = LocalUserManager.Query().In("user_id", userIds).All(&localUsers)
- if err != nil {
- return nil, err
- }
- localUserIds := []int{}
- userMap := map[string]*SLocalUser{}
- for i := range localUsers {
- user := localUsers[i]
- userMap[user.UserId] = &user
- localUserIds = append(localUserIds, user.Id)
- }
- passes := make([]SPassword, 0)
- err = PasswordManager.Query().In("local_user_id", localUserIds).All(&passes)
- if err != nil {
- return nil, errors.Wrapf(err, "Password.Query")
- }
- passwdMap := map[int]time.Time{}
- for _, pass := range passes {
- if pass.ExpiresAt.IsZero() {
- continue
- }
- if _, ok := passwdMap[pass.LocalUserId]; !ok {
- passwdMap[pass.LocalUserId] = pass.ExpiresAt
- }
- }
- idpsMaps, err := fetchIdmappings(userIds, api.IdMappingEntityUser)
- if err != nil {
- return nil, errors.Wrapf(err, "fetchIdmappings")
- }
- idpMap := map[string][]api.IdpResourceInfo{}
- for _, uid := range userIds {
- if idps, ok := idpsMaps[uid]; ok {
- data := []api.IdpResourceInfo{}
- for _, idp := range idps {
- data = append(data, idp.IdpResourceInfo)
- }
- idpMap[uid] = data
- }
- }
- result := map[string]api.UserUsage{}
- for i := range userCount {
- if user, ok := userMap[userCount[i].Id]; ok {
- userCount[i].IsLocal = true
- userCount[i].FailedAuthCount = user.FailedAuthCount
- userCount[i].FailedAuthAt = user.FailedAuthAt
- if user.NeedResetPassword.IsTrue() {
- userCount[i].NeedResetPassword = true
- userCount[i].PasswordResetHint = user.ResetHint
- }
- if expire, ok := passwdMap[userMap[userCount[i].Id].Id]; ok {
- userCount[i].PasswordExpiresAt = expire
- }
- }
- userCount[i].Idps, _ = idpMap[userCount[i].Id]
- result[userCount[i].Id] = userCount[i].UserUsage
- }
- return result, nil
- }
- func (user *SUser) initLocalData(passwd string, skipPassCheck bool) error {
- localUsr, err := LocalUserManager.register(user.Id, user.DomainId, user.Name)
- if err != nil {
- return errors.Wrap(err, "register localuser")
- }
- if len(passwd) > 0 {
- err = PasswordManager.savePassword(localUsr.Id, passwd, user.IsSystemAccount.Bool())
- if err != nil {
- return errors.Wrap(err, "save password")
- }
- if skipPassCheck {
- localUsr.markNeedResetPassword(true, api.PasswordResetHintAdminReset)
- } else {
- localUsr.markNeedResetPassword(false, "")
- }
- }
- return nil
- }
- func (user *SUser) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
- user.SEnabledIdentityBaseResource.PostCreate(ctx, userCred, ownerId, query, data)
- // set password
- passwd, _ := data.GetString("password")
- skipPassCheck := jsonutils.QueryBoolean(data, "skip_password_complexity_check", false)
- err := user.initLocalData(passwd, skipPassCheck)
- if err != nil {
- log.Errorf("fail to register localUser %s", err)
- return
- }
- // link idp
- idpId, _ := data.GetString("idp_id")
- if len(idpId) > 0 {
- idpEntityId, _ := data.GetString("idp_entity_id")
- if len(idpEntityId) > 0 {
- _, err := IdmappingManager.RegisterIdMapWithId(ctx, idpId, idpEntityId, api.IdMappingEntityUser, user.Id)
- if err != nil {
- log.Errorf("IdmappingManager.RegisterIdMapWithId fail %s", err)
- }
- }
- }
- // clean user quota
- pendingUsage := &SIdentityQuota{
- SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()},
- User: 1,
- }
- quotas.CancelPendingUsage(ctx, userCred, pendingUsage, pendingUsage, true)
- }
- func (user *SUser) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
- user.SEnabledIdentityBaseResource.PostUpdate(ctx, userCred, query, data)
- passwd, _ := data.GetString("password")
- if len(passwd) > 0 {
- usrExt, err := UserManager.FetchUserExtended(user.Id, "", "", "")
- if err != nil {
- log.Errorf("UserManager.FetchUserExtended fail %s", err)
- return
- }
- err = PasswordManager.savePassword(usrExt.LocalId, passwd, user.IsSystemAccount.Bool())
- if err != nil {
- log.Errorf("fail to set password %s", err)
- return
- }
- localUsr, err := LocalUserManager.fetchLocalUser("", "", usrExt.LocalId)
- if err != nil {
- log.Errorf("Fail to fetch localUser %d: %s", usrExt.LocalId, err)
- } else {
- skipPassCheck := jsonutils.QueryBoolean(data, "skip_password_complexity_check", false)
- if skipPassCheck {
- localUsr.markNeedResetPassword(true, api.PasswordResetHintAdminReset)
- } else {
- localUsr.markNeedResetPassword(false, "")
- }
- }
- logclient.AddActionLogWithContext(ctx, user, logclient.ACT_UPDATE_PASSWORD, nil, userCred, true)
- }
- 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)
- }
- }
- }
- }
- func (user *SUser) clearFailedAuth() error {
- localUser, err := LocalUserManager.fetchLocalUser(user.Id, user.DomainId, 0)
- if err != nil {
- if err == sql.ErrNoRows {
- return nil
- }
- return errors.Wrapf(err, "unable to fetch localUser of user %q in domain %q", user.Id, user.DomainId)
- }
- if err = localUser.ClearFailedAuth(); err != nil {
- return errors.Wrap(err, "unable to clear failed auth")
- }
- return nil
- }
- func (user *SUser) ValidateDeleteCondition(ctx context.Context, info *api.UserDetails) error {
- if user.IsAdminUser() {
- return httperrors.NewForbiddenError("cannot delete system user")
- }
- if info == nil {
- usage, err := UserManager.TotalResourceCount([]string{user.Id})
- if err != nil {
- return err
- }
- info = &api.UserDetails{}
- info.UserUsage, _ = usage[user.Id]
- }
- if !info.IsLocal && len(info.Idps) > 0 {
- for _, idp := range info.Idps {
- if !idp.IsSso {
- return httperrors.NewForbiddenError("cannot delete non-local non-sso user")
- }
- }
- }
- return user.SIdentityBaseResource.ValidateDeleteCondition(ctx, nil)
- }
- func (user *SUser) getExternalResources() (map[string]int, time.Time, error) {
- return ScopeResourceManager.getScopeResource("", "", user.Id)
- }
- func (user *SUser) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
- err := AssignmentManager.projectRemoveAllUser(ctx, userCred, user)
- if err != nil {
- return errors.Wrap(err, "AssignmentManager.projectRemoveAllUser")
- }
- err = UsergroupManager.delete(user.Id, "")
- if err != nil {
- return errors.Wrap(err, "UsergroupManager.delete")
- }
- localUser, err := LocalUserManager.delete(user.Id, user.DomainId)
- if err != nil {
- return errors.Wrap(err, "LocalUserManager.delete")
- }
- if localUser != nil {
- err = PasswordManager.delete(localUser.Id)
- if err != nil {
- return errors.Wrap(err, "PasswordManager.delete")
- }
- }
- {
- batchErr := TokenCacheManager.BatchInvalidateByUserId(ctx, userCred, user.Id)
- if batchErr != nil {
- log.Errorf("BatchInvalidateByUserId fail %s", batchErr)
- }
- }
- err = IdmappingManager.deleteByPublicId(user.Id, api.IdMappingEntityUser)
- if err != nil {
- return errors.Wrap(err, "IdmappingManager.deleteByPublicId")
- }
- return user.SEnabledIdentityBaseResource.Delete(ctx, userCred)
- }
- func (user *SUser) UpdateInContext(ctx context.Context, userCred mcclient.TokenCredential, ctxObjs []db.IModel, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- if len(ctxObjs) != 1 {
- return nil, httperrors.NewInputParameterError("not supported update context")
- }
- group, ok := ctxObjs[0].(*SGroup)
- if !ok {
- return nil, httperrors.NewInputParameterError("not supported update context %s", ctxObjs[0].Keyword())
- }
- if user.DomainId != group.DomainId {
- return nil, httperrors.NewInputParameterError("cannot join user and group in differnt domain")
- }
- if group.IsReadOnly() {
- return nil, httperrors.NewForbiddenError("cannot join read-only group")
- }
- return nil, UsergroupManager.add(ctx, userCred, user, group)
- }
- func (user *SUser) DeleteInContext(ctx context.Context, userCred mcclient.TokenCredential, ctxObjs []db.IModel, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- if len(ctxObjs) != 1 {
- return nil, httperrors.NewInputParameterError("not supported update context")
- }
- group, ok := ctxObjs[0].(*SGroup)
- if !ok {
- return nil, httperrors.NewInputParameterError("not supported update context %s", ctxObjs[0].Keyword())
- }
- if group.IsReadOnly() {
- return nil, httperrors.NewForbiddenError("cannot leave read-only group")
- }
- return nil, UsergroupManager.remove(ctx, userCred, user, group)
- }
- func (manager *SUserManager) TraceLoginV2(ctx context.Context, token *mcclient.TokenCredentialV2) {
- s := tokenV2LoginSession(token)
- manager.traceLoginEvent(ctx, token, s, token.Context)
- }
- func (manager *SUserManager) TraceLoginV3(ctx context.Context, token *mcclient.TokenCredentialV3) {
- s := tokenV3LoginSession(token)
- manager.traceLoginEvent(ctx, token, s, token.Token.Context)
- }
- func (manager *SUserManager) traceLoginEvent(ctx context.Context, token mcclient.TokenCredential, s sLoginSession, authCtx mcclient.SAuthContext) {
- usr, err := manager.fetchUserById(token.GetUserId())
- if err != nil {
- // very unlikely
- log.Errorf("fetchUserById fail %s", err)
- return
- }
- {
- err = UserLoginManager.traceLoginEvent(ctx, usr.Id, authCtx)
- if err != nil {
- log.Errorf("UserLoginManager.traceLoginEvent fail %s", err)
- }
- }
- db.OpsLog.LogEvent(usr, "auth", &s, token)
- // to reduce auth event, log web console login only
- if authCtx.Source == mcclient.AuthSourceWeb && token.GetProjectId() != "" {
- logclient.AddActionLogWithContext(ctx, usr, logclient.ACT_AUTHENTICATE, &s, token, true)
- return
- }
- // ignore any other auth source
- }
- type sLoginSession struct {
- Version string
- Source string
- Ip string
- Project string
- ProjectId string
- ProjectDomain string
- ProjectDomainId string
- Token string
- }
- func tokenV2LoginSession(token *mcclient.TokenCredentialV2) sLoginSession {
- s := sLoginSession{}
- s.Version = "v2"
- s.Source = token.Context.Source
- s.Ip = token.Context.Ip
- s.Project = token.Token.Tenant.Name
- s.ProjectId = token.Token.Tenant.Id
- s.ProjectDomain = token.Token.Tenant.Domain.Name
- s.ProjectDomainId = token.Token.Tenant.Domain.Id
- s.Token = token.Token.Id
- return s
- }
- func tokenV3LoginSession(token *mcclient.TokenCredentialV3) sLoginSession {
- s := sLoginSession{}
- s.Version = "v3"
- s.Source = token.Token.Context.Source
- s.Ip = token.Token.Context.Ip
- s.Project = token.Token.Project.Name
- s.ProjectId = token.Token.Project.Id
- s.ProjectDomain = token.Token.Project.Domain.Name
- s.ProjectDomainId = token.Token.Project.Domain.Id
- s.Token = token.Id
- return s
- }
- func (manager *SUserManager) NamespaceScope() rbacscope.TRbacScope {
- return rbacscope.ScopeDomain
- }
- func (user *SUser) getIdmappings() ([]SIdmapping, error) {
- return IdmappingManager.FetchEntities(user.Id, api.IdMappingEntityUser)
- }
- func (user *SUser) IsLocal() bool {
- usr, _ := LocalUserManager.fetchLocalUser(user.Id, user.DomainId, 0)
- if usr != nil {
- return true
- }
- return false
- }
- func (user *SUser) LinkedWithIdp(idpId string) bool {
- idmaps, _ := user.getIdmappings()
- for i := range idmaps {
- if idmaps[i].IdpId == idpId {
- return true
- }
- }
- return false
- }
- func (manager *SUserManager) FetchUsersInDomain(domainId string, excludes []string) ([]SUser, error) {
- q := manager.Query().Equals("domain_id", domainId).NotIn("id", excludes)
- usrs := make([]SUser, 0)
- err := db.FetchModelObjects(manager, q, &usrs)
- if err != nil && err != sql.ErrNoRows {
- return nil, errors.Wrap(err, "db.FetchModelObjects")
- }
- return usrs, nil
- }
- func (user *SUser) UnlinkIdp(idpId string) error {
- return IdmappingManager.deleteAny(idpId, api.IdMappingEntityUser, user.Id)
- }
- // 用户加入项目
- func (user *SUser) PerformJoin(
- ctx context.Context,
- userCred mcclient.TokenCredential,
- query jsonutils.JSONObject,
- input api.SJoinProjectsInput,
- ) (jsonutils.JSONObject, error) {
- err := joinProjects(user, true, ctx, userCred, input)
- if err != nil {
- logclient.AddActionLogWithContext(ctx, user, logclient.ACT_JOIN_PROJECT, nil, userCred, false)
- return nil, errors.Wrap(err, "joinProjects")
- }
- if input.Enabled {
- db.EnabledPerformEnable(user, ctx, userCred, true)
- }
- logclient.AddActionLogWithContext(ctx, user, logclient.ACT_JOIN_PROJECT, nil, userCred, true)
- return nil, nil
- }
- func joinProjects(ident db.IModel, isUser bool, ctx context.Context, userCred mcclient.TokenCredential, input api.SJoinProjectsInput) error {
- err := input.Validate()
- if err != nil {
- return httperrors.NewInputParameterError("%v", err)
- }
- projects := make([]*SProject, 0)
- roles := make([]*SRole, 0)
- roleIds := make([]string, 0)
- for i := range input.Roles {
- obj, err := RoleManager.FetchByIdOrName(ctx, userCred, input.Roles[i])
- if err != nil {
- if err == sql.ErrNoRows {
- return httperrors.NewResourceNotFoundError2(RoleManager.Keyword(), input.Roles[i])
- } else {
- return httperrors.NewGeneralError(err)
- }
- }
- role := obj.(*SRole)
- roles = append(roles, role)
- roleIds = append(roleIds, role.Id)
- }
- for i := range input.Projects {
- obj, err := ProjectManager.FetchByIdOrName(ctx, userCred, input.Projects[i])
- if err != nil {
- if err == sql.ErrNoRows {
- return httperrors.NewResourceNotFoundError2(ProjectManager.Keyword(), input.Projects[i])
- } else {
- return httperrors.NewGeneralError(err)
- }
- }
- project := obj.(*SProject)
- err = validateJoinProject(userCred, project, roleIds)
- if err != nil {
- return errors.Wrapf(err, "validateJoinProject %s(%s)", project.Id, project.Name)
- }
- projects = append(projects, project)
- }
- for i := range projects {
- for j := range roles {
- if isUser {
- err = AssignmentManager.ProjectAddUser(ctx, userCred, projects[i], ident.(*SUser), roles[j])
- } else {
- err = AssignmentManager.projectAddGroup(ctx, userCred, projects[i], ident.(*SGroup), roles[j])
- }
- if err != nil {
- return httperrors.NewGeneralError(err)
- }
- }
- }
- return nil
- }
- // 用户退出项目
- func (user *SUser) PerformLeave(
- ctx context.Context,
- userCred mcclient.TokenCredential,
- query jsonutils.JSONObject,
- input api.SLeaveProjectsInput,
- ) (jsonutils.JSONObject, error) {
- err := leaveProjects(user, true, ctx, userCred, input)
- if err != nil {
- logclient.AddActionLogWithContext(ctx, user, logclient.ACT_LEAVE_PROJECT, nil, userCred, false)
- return nil, err
- }
- logclient.AddActionLogWithContext(ctx, user, logclient.ACT_LEAVE_PROJECT, nil, userCred, true)
- return nil, nil
- }
- func leaveProjects(ident db.IModel, isUser bool, ctx context.Context, userCred mcclient.TokenCredential, input api.SLeaveProjectsInput) error {
- for i := range input.ProjectRoles {
- projObj, err := ProjectManager.FetchByIdOrName(ctx, userCred, input.ProjectRoles[i].Project)
- if err != nil {
- if err == sql.ErrNoRows {
- return httperrors.NewResourceNotFoundError2(ProjectManager.Keyword(), input.ProjectRoles[i].Project)
- } else {
- return httperrors.NewGeneralError(err)
- }
- }
- roleObj, err := RoleManager.FetchByIdOrName(ctx, userCred, input.ProjectRoles[i].Role)
- if err != nil {
- if err == sql.ErrNoRows {
- return httperrors.NewResourceNotFoundError2(RoleManager.Keyword(), input.ProjectRoles[i].Role)
- } else {
- return httperrors.NewGeneralError(err)
- }
- }
- if isUser {
- err = AssignmentManager.projectRemoveUser(ctx, userCred, projObj.(*SProject), ident.(*SUser), roleObj.(*SRole))
- } else {
- err = AssignmentManager.projectRemoveGroup(ctx, userCred, projObj.(*SProject), ident.(*SGroup), roleObj.(*SRole))
- }
- if err != nil {
- return httperrors.NewGeneralError(err)
- }
- }
- return nil
- }
- func (manager *SUserManager) LockUser(uid string, reason string) error {
- usrObj, err := manager.FetchById(uid)
- if err != nil {
- return errors.Wrapf(err, "manager.FetchById %s", uid)
- }
- usr := usrObj.(*SUser)
- _, err = db.Update(usr, func() error {
- usr.Enabled = tristate.False
- return nil
- })
- if err != nil {
- return errors.Wrap(err, "Update")
- }
- db.OpsLog.LogEvent(usr, db.ACT_DISABLE, reason, GetDefaultAdminCred())
- logclient.AddSimpleActionLog(usr, logclient.ACT_DISABLE, reason, GetDefaultAdminCred(), false)
- return nil
- }
- func (manager *SUserManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man db.FilterByOwnerProvider, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
- log.Debugf("owner: %s scope %s", jsonutils.Marshal(owner), scope)
- if owner != nil && scope == rbacscope.ScopeProject {
- // if user has project level privilege, returns all users in user's project
- subq := AssignmentManager.fetchProjectUserIdsQuery(owner.GetProjectId())
- q = q.In("id", subq.SubQuery())
- return q
- }
- return manager.SEnabledIdentityBaseResourceManager.FilterByOwner(ctx, q, man, userCred, owner, scope)
- }
- func (user *SUser) GetUsages() []db.IUsage {
- usage := SIdentityQuota{
- SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: user.DomainId},
- User: 1,
- }
- return []db.IUsage{
- &usage,
- }
- }
- // 用户和IDP的指定entityId关联
- func (user *SUser) PerformLinkIdp(
- ctx context.Context,
- userCred mcclient.TokenCredential,
- query jsonutils.JSONObject,
- input api.UserLinkIdpInput,
- ) (jsonutils.JSONObject, error) {
- idp, err := IdentityProviderManager.FetchIdentityProviderById(input.IdpId)
- if err != nil {
- if errors.Cause(err) == sql.ErrNoRows {
- return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", IdentityProviderManager.Keyword(), input.IdpId)
- } else {
- return nil, errors.Wrap(err, "IdentityProviderManager.FetchIdentityProviderById")
- }
- }
- // check accessibility
- if (len(idp.DomainId) > 0 && idp.DomainId != user.DomainId) || (len(idp.TargetDomainId) > 0 && idp.TargetDomainId != user.DomainId) {
- return nil, errors.Wrap(httperrors.ErrForbidden, "identity domain not accessible")
- } else if len(idp.DomainId) == 0 && len(idp.TargetDomainId) == 0 && idp.AutoCreateUser.IsTrue() {
- }
- _, err = IdmappingManager.RegisterIdMapWithId(ctx, input.IdpId, input.IdpEntityId, api.IdMappingEntityUser, user.Id)
- if err != nil {
- return nil, errors.Wrap(err, "IdmappingManager.RegisterIdMapWithId")
- }
- return nil, nil
- }
- // 用户和IDP的指定entityId解除关联
- func (user *SUser) PerformUnlinkIdp(
- ctx context.Context,
- userCred mcclient.TokenCredential,
- query jsonutils.JSONObject,
- input api.UserUnlinkIdpInput,
- ) (jsonutils.JSONObject, error) {
- err := IdmappingManager.deleteAny(input.IdpId, api.IdMappingEntityUser, user.Id)
- if err != nil {
- return nil, errors.Wrap(err, "IdmappingManager.deleteAny")
- }
- return nil, nil
- }
- func GetUserLangForKeyStone(uids []string) (map[string]string, error) {
- simpleUsers := make([]struct {
- Id string
- Lang string
- }, 0, len(uids))
- q := UserManager.Query()
- if len(uids) == 0 {
- return nil, nil
- } else if len(uids) == 1 {
- q = q.Equals("id", uids[0])
- } else {
- q = q.In("id", uids)
- }
- err := q.All(&simpleUsers)
- if err != nil {
- return nil, err
- }
- ret := make(map[string]string, len(simpleUsers))
- for i := range simpleUsers {
- ret[simpleUsers[i].Id] = simpleUsers[i].Lang
- }
- return ret, nil
- }
- // 用户加入项目
- func (user *SUser) PerformResetCredentials(
- ctx context.Context,
- userCred mcclient.TokenCredential,
- query jsonutils.JSONObject,
- input api.ResetCredentialInput,
- ) (jsonutils.JSONObject, error) {
- err := CredentialManager.DeleteAll(ctx, userCred, user.Id, input.Type)
- if err != nil {
- return nil, errors.Wrapf(err, "DeleteAll %s", input.Type)
- }
- if input.Type == api.TOTP_TYPE {
- err := CredentialManager.DeleteAll(ctx, userCred, user.Id, api.RECOVERY_SECRETS_TYPE)
- if err != nil {
- return nil, errors.Wrapf(err, "DeleteAll %s", api.RECOVERY_SECRETS_TYPE)
- }
- }
- logclient.AddActionLogWithContext(ctx, user, logclient.ACT_RESET_CREDENTIAL, nil, userCred, true)
- return nil, nil
- }
- func (user *SUser) PerformEnable(
- ctx context.Context,
- userCred mcclient.TokenCredential,
- query jsonutils.JSONObject,
- input apis.PerformEnableInput,
- ) (jsonutils.JSONObject, error) {
- err := user.clearFailedAuth()
- if err != nil {
- log.Errorf("clearFailedAuth %s", err)
- }
- 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
- }
|