users.go 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package models
  15. import (
  16. "context"
  17. "database/sql"
  18. "fmt"
  19. "time"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/log"
  22. "yunion.io/x/pkg/errors"
  23. "yunion.io/x/pkg/tristate"
  24. "yunion.io/x/pkg/util/rbacscope"
  25. "yunion.io/x/sqlchemy"
  26. "yunion.io/x/onecloud/pkg/apis"
  27. api "yunion.io/x/onecloud/pkg/apis/identity"
  28. "yunion.io/x/onecloud/pkg/cloudcommon/consts"
  29. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  30. "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
  31. "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
  32. "yunion.io/x/onecloud/pkg/cloudcommon/policy"
  33. "yunion.io/x/onecloud/pkg/httperrors"
  34. "yunion.io/x/onecloud/pkg/keystone/options"
  35. o "yunion.io/x/onecloud/pkg/keystone/options"
  36. "yunion.io/x/onecloud/pkg/mcclient"
  37. "yunion.io/x/onecloud/pkg/util/logclient"
  38. "yunion.io/x/onecloud/pkg/util/seclib2"
  39. "yunion.io/x/onecloud/pkg/util/stringutils2"
  40. )
  41. type SUserManager struct {
  42. SEnabledIdentityBaseResourceManager
  43. db.SRecordChecksumResourceBaseManager
  44. }
  45. var UserManager *SUserManager
  46. func init() {
  47. UserManager = &SUserManager{
  48. SEnabledIdentityBaseResourceManager: NewEnabledIdentityBaseResourceManager(
  49. SUser{},
  50. "user",
  51. "user",
  52. "users",
  53. ),
  54. SRecordChecksumResourceBaseManager: *db.NewRecordChecksumResourceBaseManager(),
  55. }
  56. UserManager.SetVirtualObject(UserManager)
  57. db.InitManager(func() {
  58. UserManager.TableSpec().ColumnSpec("lang").SetDefault(options.Options.DefaultUserLanguage)
  59. })
  60. notifyclient.AddNotifyDBHookResources(UserManager.KeywordPlural())
  61. }
  62. /*
  63. +--------------------+-------------+------+-----+---------+-------+
  64. | Field | Type | Null | Key | Default | Extra |
  65. +--------------------+-------------+------+-----+---------+-------+
  66. | id | varchar(64) | NO | PRI | NULL | |
  67. | extra | text | YES | | NULL | |
  68. | enabled | tinyint(1) | YES | | NULL | |
  69. | default_project_id | varchar(64) | YES | MUL | NULL | |
  70. | created_at | datetime | YES | | NULL | |
  71. | last_active_at | date | YES | | NULL | |
  72. | domain_id | varchar(64) | NO | MUL | NULL | |
  73. +--------------------+-------------+------+-----+---------+-------+
  74. */
  75. type SUser struct {
  76. db.SRecordChecksumResourceBase
  77. SEnabledIdentityBaseResource
  78. // 用户邮箱
  79. Email string `width:"64" charset:"utf8" nullable:"true" index:"true" list:"domain" update:"domain" create:"domain_optional"`
  80. // 用户手机号
  81. Mobile string `width:"20" charset:"utf8" nullable:"true" index:"true" list:"domain" update:"domain" create:"domain_optional"`
  82. // 显示名称,用户登录后显示在右上角菜单入口
  83. Displayname string `with:"128" charset:"utf8" nullable:"true" list:"domain" update:"domain" create:"domain_optional"`
  84. // 上次登录时间
  85. // deprecated
  86. // swagger:ignore
  87. LastActiveAt time.Time `nullable:"true" list:"domain"`
  88. // 上次用户登录IP
  89. // deprecated
  90. // swagger:ignore
  91. LastLoginIp string `nullable:"true" list:"domain"`
  92. // 上次用户登录方式,可能值有:web(web控制台),cli(命令行climc),API(api)
  93. // deprecated
  94. // swagger:ignore
  95. LastLoginSource string `nullable:"true" list:"domain"`
  96. // 是否为系统账号,系统账号不会检查密码复杂度,默认不在列表显示
  97. IsSystemAccount tristate.TriState `default:"false" list:"domain" update:"admin" create:"admin_optional"`
  98. // deprecated
  99. DefaultProjectId string `width:"64" charset:"ascii" nullable:"true"`
  100. // 是否允许登录Web控制台,如果是用于API访问的用户,可禁用web控制台登录
  101. AllowWebConsole tristate.TriState `default:"true" list:"domain" update:"domain" create:"domain_optional"`
  102. // 是否开启MFA
  103. EnableMfa tristate.TriState `default:"false" list:"domain" update:"domain" create:"domain_optional"`
  104. // 用户的默认语言设置,默认是zh_CN
  105. Lang string `width:"8" charset:"ascii" nullable:"false" list:"domain" update:"domain" create:"domain_optional"`
  106. // 过期时间
  107. ExpiredAt time.Time `nullable:"true" list:"domain" update:"domain" create:"domain_optional"`
  108. }
  109. func (manager *SUserManager) GetContextManagers() [][]db.IModelManager {
  110. return [][]db.IModelManager{
  111. {GroupManager},
  112. {ProjectManager},
  113. }
  114. }
  115. func (manager *SUserManager) InitializeData() error {
  116. q := manager.Query().IsNullOrEmpty("name")
  117. users := make([]SUser, 0)
  118. err := db.FetchModelObjects(manager, q, &users)
  119. if err != nil {
  120. return errors.Wrap(err, "FetchModelObjects")
  121. }
  122. for i := range users {
  123. extUser, err := manager.FetchUserExtended(users[i].Id, "", "", "")
  124. if err != nil {
  125. return errors.Wrap(err, "FetchUserExtended")
  126. }
  127. name := extUser.LocalName
  128. if len(name) == 0 {
  129. name = extUser.DomainName
  130. }
  131. var desc, email, mobile, dispName string
  132. if users[i].Extra != nil {
  133. desc, _ = users[i].Extra.GetString("description")
  134. email, _ = users[i].Extra.GetString("email")
  135. mobile, _ = users[i].Extra.GetString("mobile")
  136. dispName, _ = users[i].Extra.GetString("displayname")
  137. }
  138. _, err = db.Update(&users[i], func() error {
  139. users[i].Name = name
  140. if len(email) > 0 {
  141. users[i].Email = email
  142. }
  143. if len(mobile) > 0 {
  144. users[i].Mobile = mobile
  145. }
  146. if len(dispName) > 0 {
  147. users[i].Displayname = dispName
  148. }
  149. if len(desc) > 0 {
  150. users[i].Description = desc
  151. }
  152. return nil
  153. })
  154. if err != nil {
  155. return errors.Wrap(err, "update")
  156. }
  157. }
  158. {
  159. err := manager.initSystemAccount()
  160. if err != nil {
  161. return errors.Wrap(err, "initSystemAccount")
  162. }
  163. }
  164. {
  165. err := manager.initSysUser(context.TODO())
  166. if err != nil {
  167. return errors.Wrap(err, "initSystemAccount")
  168. }
  169. }
  170. {
  171. err := manager.migrateUserLogin()
  172. if err != nil {
  173. return errors.Wrap(err, "migrateUserLogin")
  174. }
  175. }
  176. return nil
  177. }
  178. func (manager *SUserManager) migrateUserLogin() error {
  179. userLoginQ := UserLoginManager.Query("user_id").SubQuery()
  180. q := manager.Query().NotIn("id", userLoginQ)
  181. rows, err := q.Rows()
  182. if err != nil {
  183. return errors.Wrap(err, "query.Rows")
  184. }
  185. defer rows.Close()
  186. type SUserLoginExt struct {
  187. SUserLogin
  188. Id string
  189. }
  190. for rows.Next() {
  191. userLogin := SUserLoginExt{}
  192. err := q.Row2Struct(rows, &userLogin)
  193. if err != nil {
  194. return errors.Wrap(err, "row2struct")
  195. }
  196. userLogin.UserId = userLogin.Id
  197. userLogin.SUserLogin.SetModelManager(UserLoginManager, &userLogin.SUserLogin)
  198. err = UserLoginManager.TableSpec().Insert(context.Background(), &userLogin.SUserLogin)
  199. if err != nil {
  200. return errors.Wrap(err, "insert")
  201. }
  202. }
  203. 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())
  204. _, err = manager.TableSpec().GetTableSpec().Database().Exec(sql)
  205. if err != nil {
  206. return errors.Wrap(err, "exec batch update")
  207. }
  208. return nil
  209. }
  210. func (manager *SUserManager) initSystemAccount() error {
  211. q := manager.Query().IsNotEmpty("default_project_id")
  212. users := make([]SUser, 0)
  213. err := db.FetchModelObjects(manager, q, &users)
  214. if err != nil {
  215. return errors.Wrap(err, "FetchModelObjects")
  216. }
  217. for i := range users {
  218. _, err = db.Update(&users[i], func() error {
  219. users[i].IsSystemAccount = tristate.True
  220. users[i].DefaultProjectId = ""
  221. return nil
  222. })
  223. if err != nil {
  224. return errors.Wrap(err, "update")
  225. }
  226. }
  227. return nil
  228. }
  229. func (manager *SUserManager) initSysUser(ctx context.Context) error {
  230. q := manager.Query().Equals("name", api.SystemAdminUser)
  231. q = q.Equals("domain_id", api.DEFAULT_DOMAIN_ID)
  232. cnt, err := q.CountWithError()
  233. if err != nil {
  234. return errors.Wrap(err, "query")
  235. }
  236. if cnt == 1 {
  237. // if ResetAdminUserPassword is true, reset sysadmin password
  238. if o.Options.ResetAdminUserPassword {
  239. usr := SUser{}
  240. usr.SetModelManager(manager, &usr)
  241. err = q.First(&usr)
  242. if err != nil {
  243. return errors.Wrap(err, "ResetAdminUserPassword Query user")
  244. }
  245. err = usr.initLocalData(o.Options.BootstrapAdminUserPassword, false)
  246. if err != nil {
  247. return errors.Wrap(err, "initLocalData")
  248. }
  249. }
  250. return nil
  251. }
  252. if cnt > 2 {
  253. // ???
  254. log.Fatalf("duplicate sysadmin account???")
  255. }
  256. // insert
  257. usr := SUser{}
  258. usr.Name = api.SystemAdminUser
  259. usr.DomainId = api.DEFAULT_DOMAIN_ID
  260. usr.Enabled = tristate.True
  261. usr.IsSystemAccount = tristate.True
  262. usr.AllowWebConsole = tristate.False
  263. usr.EnableMfa = tristate.False
  264. usr.Description = "Boostrap system default admin user"
  265. usr.SetModelManager(manager, &usr)
  266. err = manager.TableSpec().Insert(ctx, &usr)
  267. if err != nil {
  268. return errors.Wrap(err, "insert")
  269. }
  270. err = usr.initLocalData(o.Options.BootstrapAdminUserPassword, false)
  271. if err != nil {
  272. return errors.Wrap(err, "initLocalData")
  273. }
  274. return nil
  275. }
  276. func (manager *SUserManager) EnforceUserMfa(ctx context.Context) error {
  277. if options.Options.ForceEnableMfa != "all" {
  278. return nil
  279. }
  280. q := manager.Query().IsFalse("enable_mfa")
  281. users := make([]SUser, 0)
  282. err := db.FetchModelObjects(manager, q, &users)
  283. if err != nil {
  284. return errors.Wrap(err, "FetchModelObjects")
  285. }
  286. for i := range users {
  287. _, err := db.Update(&users[i], func() error {
  288. users[i].EnableMfa = tristate.True
  289. return nil
  290. })
  291. if err != nil {
  292. return errors.Wrap(err, "update enable mfa")
  293. }
  294. logclient.AddSimpleActionLog(&users[i], logclient.ACT_UPDATE, "force enable mfa", GetDefaultAdminCred(), true)
  295. }
  296. return nil
  297. }
  298. /*
  299. Fetch extended userinfo by Id or name + domainId or name + domainName
  300. */
  301. func (manager *SUserManager) FetchUserExtended(userId, userName, domainId, domainName string) (*api.SUserExtended, error) {
  302. if len(userId) == 0 && len(userName) == 0 {
  303. return nil, sqlchemy.ErrEmptyQuery
  304. }
  305. localUsers := LocalUserManager.Query().SubQuery()
  306. // nonlocalUsers := NonlocalUserManager.Query().SubQuery()
  307. users := UserManager.Query().SubQuery()
  308. domains := DomainManager.Query().SubQuery()
  309. // idmappings := IdmappingManager.Query().SubQuery()
  310. q := users.Query(
  311. users.Field("id"),
  312. users.Field("name"),
  313. users.Field("displayname"),
  314. users.Field("email"),
  315. users.Field("mobile"),
  316. users.Field("enabled"),
  317. users.Field("default_project_id"),
  318. users.Field("created_at"),
  319. users.Field("last_active_at"),
  320. users.Field("domain_id"),
  321. users.Field("is_system_account"),
  322. users.Field("expired_at"),
  323. localUsers.Field("id", "local_id"),
  324. localUsers.Field("name", "local_name"),
  325. localUsers.Field("failed_auth_count", "local_failed_auth_count"),
  326. domains.Field("name", "domain_name"),
  327. domains.Field("enabled", "domain_enabled"),
  328. // idmappings.Field("domain_id", "idp_id"),
  329. // idmappings.Field("local_id", "idp_name"),
  330. )
  331. q = q.Join(domains, sqlchemy.Equals(users.Field("domain_id"), domains.Field("id")))
  332. q = q.LeftJoin(localUsers, sqlchemy.Equals(localUsers.Field("user_id"), users.Field("id")))
  333. // q = q.LeftJoin(idmappings, sqlchemy.Equals(users.Field("id"), idmappings.Field("public_id")))
  334. if len(userId) > 0 {
  335. q = q.Filter(sqlchemy.Equals(users.Field("id"), userId))
  336. } else if len(userName) > 0 {
  337. q = q.Filter(sqlchemy.Equals(users.Field("name"), userName))
  338. if len(domainId) == 0 && len(domainName) == 0 {
  339. domainId = api.DEFAULT_DOMAIN_ID
  340. }
  341. if len(domainId) > 0 {
  342. q = q.Filter(sqlchemy.Equals(domains.Field("id"), domainId))
  343. } else if len(domainName) > 0 {
  344. q = q.Filter(sqlchemy.Equals(domains.Field("name"), domainName))
  345. }
  346. }
  347. extUser := api.SUserExtended{}
  348. err := q.First(&extUser)
  349. if err != nil {
  350. if err == sql.ErrNoRows {
  351. return nil, httperrors.ErrUserNotFound
  352. }
  353. return nil, errors.Wrap(err, "query")
  354. }
  355. if extUser.LocalId > 0 {
  356. extUser.IsLocal = true
  357. }
  358. return &extUser, nil
  359. }
  360. func VerifyPassword(user *api.SUserExtended, passwd string) error {
  361. return localUserVerifyPassword(user, passwd)
  362. }
  363. func localUserVerifyPassword(user *api.SUserExtended, passwd string) error {
  364. pass, err := PasswordManager.FetchByLocaluserIdNewestPassword(user.LocalId)
  365. if err != nil {
  366. return errors.Wrap(err, "fetchPassword")
  367. }
  368. if pass == nil {
  369. return errors.Error("no valid password")
  370. }
  371. // password expiration check skip system account
  372. // if passes[0].IsExpired() && !user.IsSystemAccount {
  373. // return errors.Error("password expires")
  374. // }
  375. // password expires, no error returns but set user need to reset password silently
  376. if pass.IsExpired() {
  377. localUsr, err := LocalUserManager.fetchLocalUser("", "", user.LocalId)
  378. if err != nil {
  379. return errors.Wrap(err, "fetchLocalUser")
  380. }
  381. localUsr.markNeedResetPassword(true, api.PasswordResetHintExpire)
  382. }
  383. err = seclib2.BcryptVerifyPassword(passwd, pass.PasswordHash)
  384. if err == nil {
  385. return nil
  386. }
  387. return httperrors.ErrWrongPassword
  388. }
  389. // 用户列表
  390. func (manager *SUserManager) ListItemFilter(
  391. ctx context.Context,
  392. q *sqlchemy.SQuery,
  393. userCred mcclient.TokenCredential,
  394. query api.UserListInput,
  395. ) (*sqlchemy.SQuery, error) {
  396. q, err := manager.SEnabledIdentityBaseResourceManager.ListItemFilter(ctx, q, userCred, query.EnabledIdentityBaseResourceListInput)
  397. if err != nil {
  398. return nil, errors.Wrap(err, "SEnabledIdentityBaseResourceManager.ListItemFilter")
  399. }
  400. if len(query.Email) > 0 {
  401. q = q.In("email", query.Email)
  402. }
  403. if len(query.Mobile) > 0 {
  404. q = q.In("mobile", query.Mobile)
  405. }
  406. if len(query.Displayname) > 0 {
  407. q = q.In("displayname", query.Displayname)
  408. }
  409. if query.AllowWebConsole != nil {
  410. if *query.AllowWebConsole {
  411. q = q.IsTrue("allow_web_console")
  412. } else {
  413. q = q.IsFalse("allow_web_console")
  414. }
  415. }
  416. if query.EnableMfa != nil {
  417. if *query.EnableMfa {
  418. q = q.IsTrue("enable_mfa")
  419. } else {
  420. q = q.IsFalse("enable_mfa")
  421. }
  422. }
  423. groupStr := query.GroupId
  424. if len(groupStr) > 0 {
  425. groupObj, err := GroupManager.FetchByIdOrName(ctx, userCred, groupStr)
  426. if err != nil {
  427. if err == sql.ErrNoRows {
  428. return nil, httperrors.NewResourceNotFoundError2(GroupManager.Keyword(), groupStr)
  429. } else {
  430. return nil, httperrors.NewGeneralError(err)
  431. }
  432. }
  433. subq := UsergroupManager.Query("user_id").Equals("group_id", groupObj.GetId())
  434. q = q.In("id", subq.SubQuery())
  435. }
  436. projectStr := query.ProjectId
  437. if len(projectStr) > 0 {
  438. project, err := ProjectManager.FetchByIdOrName(ctx, userCred, projectStr)
  439. if err != nil {
  440. if err == sql.ErrNoRows {
  441. return nil, httperrors.NewResourceNotFoundError2(ProjectManager.Keyword(), projectStr)
  442. } else {
  443. return nil, httperrors.NewGeneralError(err)
  444. }
  445. }
  446. subq := AssignmentManager.fetchProjectUserIdsQuery(project.GetId())
  447. q = q.In("id", subq.SubQuery())
  448. }
  449. roleStr := query.RoleId
  450. if len(roleStr) > 0 {
  451. role, err := RoleManager.FetchByIdOrName(ctx, userCred, roleStr)
  452. if err != nil {
  453. if err == sql.ErrNoRows {
  454. return nil, httperrors.NewResourceNotFoundError2(RoleManager.Keyword(), roleStr)
  455. } else {
  456. return nil, httperrors.NewGeneralError(err)
  457. }
  458. }
  459. subq := AssignmentManager.Query("actor_id").Equals("role_id", role.GetId()).Equals("type", api.AssignmentUserProject).Distinct()
  460. if len(query.RoleAssignmentDomainId) > 0 {
  461. domain, err := DomainManager.FetchByIdOrName(ctx, userCred, query.RoleAssignmentDomainId)
  462. if err != nil {
  463. if err == sql.ErrNoRows {
  464. return nil, httperrors.NewResourceNotFoundError2(DomainManager.Keyword(), query.RoleAssignmentDomainId)
  465. } else {
  466. return nil, httperrors.NewGeneralError(err)
  467. }
  468. }
  469. projects := ProjectManager.Query("id").Equals("domain_id", domain.GetId()).SubQuery()
  470. subq = subq.In("target_id", projects.Query())
  471. }
  472. if len(query.RoleAssignmentProjectId) > 0 {
  473. project, err := ProjectManager.FetchByIdOrName(ctx, userCred, query.RoleAssignmentProjectId)
  474. if err != nil {
  475. if err == sql.ErrNoRows {
  476. return nil, httperrors.NewResourceNotFoundError2(ProjectManager.Keyword(), query.RoleAssignmentProjectId)
  477. } else {
  478. return nil, httperrors.NewGeneralError(err)
  479. }
  480. }
  481. subq = subq.Equals("target_id", project.GetId())
  482. }
  483. q = q.In("id", subq.SubQuery().Query())
  484. }
  485. if len(query.IdpId) > 0 {
  486. idpObj, err := IdentityProviderManager.FetchByIdOrName(ctx, userCred, query.IdpId)
  487. if err != nil {
  488. if errors.Cause(err) == sql.ErrNoRows {
  489. return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", IdentityProviderManager.Keyword(), query.IdpId)
  490. } else {
  491. return nil, errors.Wrap(err, "IdentityProviderManager.FetchByIdOrName")
  492. }
  493. }
  494. subq := IdmappingManager.FetchPublicIdsExcludesQuery(idpObj.GetId(), api.IdMappingEntityUser, nil)
  495. q = q.In("id", subq.SubQuery())
  496. }
  497. if len(query.IdpEntityId) > 0 {
  498. subq := IdmappingManager.Query("public_id").Equals("local_id", query.IdpEntityId).Equals("entity_type", api.IdMappingEntityUser)
  499. q = q.Equals("id", subq.SubQuery())
  500. }
  501. return q, nil
  502. }
  503. func (manager *SUserManager) OrderByExtraFields(
  504. ctx context.Context,
  505. q *sqlchemy.SQuery,
  506. userCred mcclient.TokenCredential,
  507. query api.UserListInput,
  508. ) (*sqlchemy.SQuery, error) {
  509. var err error
  510. q, err = manager.SEnabledIdentityBaseResourceManager.OrderByExtraFields(ctx, q, userCred, query.EnabledIdentityBaseResourceListInput)
  511. if err != nil {
  512. return nil, errors.Wrap(err, "SEnabledIdentityBaseResourceManager.OrderByExtraFields")
  513. }
  514. return q, nil
  515. }
  516. func (manager *SUserManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  517. var err error
  518. q, err = manager.SEnabledIdentityBaseResourceManager.QueryDistinctExtraField(q, field)
  519. if err == nil {
  520. return q, nil
  521. }
  522. return q, httperrors.ErrNotFound
  523. }
  524. func (manager *SUserManager) FilterByHiddenSystemAttributes(q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
  525. q = manager.SEnabledIdentityBaseResourceManager.FilterByHiddenSystemAttributes(q, userCred, query, scope)
  526. isSystem := jsonutils.QueryBoolean(query, "system", false)
  527. if isSystem {
  528. var isAllow bool
  529. allowScope, _ := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), manager.KeywordPlural(), policy.PolicyActionList, "system")
  530. if !scope.HigherThan(allowScope) {
  531. isAllow = true
  532. }
  533. if !isAllow {
  534. isSystem = false
  535. }
  536. }
  537. if !isSystem {
  538. q = q.Filter(sqlchemy.OR(sqlchemy.IsNull(q.Field("is_system_account")), sqlchemy.IsFalse(q.Field("is_system_account"))))
  539. }
  540. return q
  541. }
  542. func (manager *SUserManager) ValidateCreateData(
  543. ctx context.Context,
  544. userCred mcclient.TokenCredential,
  545. ownerId mcclient.IIdentityProvider,
  546. query jsonutils.JSONObject,
  547. input api.UserCreateInput,
  548. ) (api.UserCreateInput, error) {
  549. var err error
  550. if len(input.Password) > 0 && (input.SkipPasswordComplexityCheck == nil || !*input.SkipPasswordComplexityCheck) {
  551. err = validatePasswordComplexity(input.Password)
  552. if err != nil {
  553. return input, errors.Wrap(err, "validatePasswordComplexity")
  554. }
  555. }
  556. input.EnabledIdentityBaseResourceCreateInput, err = manager.SEnabledIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EnabledIdentityBaseResourceCreateInput)
  557. if err != nil {
  558. return input, errors.Wrap(err, "SEnabledIdentityBaseResourceManager.ValidateCreateData")
  559. }
  560. if len(input.IdpId) > 0 {
  561. _, err := IdentityProviderManager.FetchIdentityProviderById(input.IdpId)
  562. if err != nil {
  563. if errors.Cause(err) == sql.ErrNoRows {
  564. return input, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", IdentityProviderManager.Keyword(), input.IdpId)
  565. } else {
  566. return input, errors.Wrap(err, "IdentityProviderManager.FetchIdentityProviderById")
  567. }
  568. }
  569. }
  570. quota := SIdentityQuota{
  571. SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()},
  572. User: 1,
  573. }
  574. err = quotas.CheckSetPendingQuota(ctx, userCred, &quota)
  575. if err != nil {
  576. return input, errors.Wrapf(err, "CheckSetPendingQuota")
  577. }
  578. if options.Options.ForceEnableMfa != "disable" {
  579. boolTrue := true
  580. input.EnableMfa = &boolTrue
  581. }
  582. return input, nil
  583. }
  584. func (user *SUser) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.UserUpdateInput) (api.UserUpdateInput, error) {
  585. if len(input.Name) > 0 {
  586. if user.IsAdminUser() {
  587. return input, httperrors.NewForbiddenError("cannot alter sysadmin user name")
  588. }
  589. }
  590. if !user.IsLocal() {
  591. data := jsonutils.Marshal(input)
  592. for _, k := range []string{
  593. "name",
  594. // "displayname",
  595. // "email",
  596. // "mobile",
  597. "password",
  598. } {
  599. if data.Contains(k) {
  600. return input, httperrors.NewForbiddenError("field %s is readonly", k)
  601. }
  602. }
  603. }
  604. if len(input.Password) > 0 && (input.SkipPasswordComplexityCheck == nil || *input.SkipPasswordComplexityCheck == false) {
  605. passwd := input.Password
  606. usrExt, err := UserManager.FetchUserExtended(user.Id, "", "", "")
  607. if err != nil {
  608. return input, errors.Wrap(err, "UserManager.FetchUserExtended")
  609. }
  610. if !usrExt.IsLocal {
  611. return input, errors.Wrap(httperrors.ErrForbidden, "cannot update password for non-local user")
  612. }
  613. skipHistoryCheck := false
  614. if user.IsSystemAccount.Bool() {
  615. skipHistoryCheck = true
  616. }
  617. err = PasswordManager.validatePassword(usrExt.LocalId, passwd, skipHistoryCheck)
  618. if err != nil {
  619. return input, httperrors.NewInputParameterError("invalid password: %s", err)
  620. }
  621. }
  622. if options.Options.ForceEnableMfa != "disable" {
  623. boolTrue := true
  624. input.EnableMfa = &boolTrue
  625. }
  626. if input.ClearExpire != nil && *input.ClearExpire {
  627. tmZero := time.Time{}
  628. input.ExpiredAt = &tmZero
  629. }
  630. var err error
  631. input.EnabledIdentityBaseUpdateInput, err = user.SEnabledIdentityBaseResource.ValidateUpdateData(ctx, userCred, query, input.EnabledIdentityBaseUpdateInput)
  632. if err != nil {
  633. return input, errors.Wrap(err, "SEnabledIdentityBaseResource.ValidateUpdateData")
  634. }
  635. return input, nil
  636. }
  637. func (user *SUser) ValidateUpdateCondition(ctx context.Context) error {
  638. // if user.IsReadOnly() {
  639. // return httperrors.NewForbiddenError("readonly")
  640. // }
  641. return user.SEnabledIdentityBaseResource.ValidateUpdateCondition(ctx)
  642. }
  643. func (manager *SUserManager) fetchUserById(uid string) (*SUser, error) {
  644. obj, err := manager.FetchById(uid)
  645. if err != nil {
  646. return nil, errors.Wrap(err, "manager.FetchById")
  647. }
  648. return obj.(*SUser), nil
  649. }
  650. func (user *SUser) IsAdminUser() bool {
  651. return user.Name == api.SystemAdminUser && user.DomainId == api.DEFAULT_DOMAIN_ID
  652. }
  653. func (user *SUser) GetGroupCount() (int, error) {
  654. q := UsergroupManager.Query().Equals("user_id", user.Id)
  655. return q.CountWithError()
  656. }
  657. func (user *SUser) GetProjectCount() (int, error) {
  658. q := AssignmentManager.fetchUserProjectIdsQuery(user.Id)
  659. return q.CountWithError()
  660. }
  661. func (user *SUser) GetCredentialCount() (int, error) {
  662. q := CredentialManager.Query().Equals("user_id", user.Id)
  663. return q.CountWithError()
  664. }
  665. func (manager *SUserManager) FetchScopeResources(userIds []string) (map[string]api.ExternalResourceInfo, error) {
  666. resources := ScopeResourceManager.Query().In("owner_id", userIds).SubQuery()
  667. q := resources.Query(
  668. resources.Field("resource"),
  669. resources.Field("owner_id"),
  670. sqlchemy.SUM("res_count", resources.Field("count")),
  671. sqlchemy.MAX("last_update", resources.Field("updated_at")),
  672. )
  673. q = q.GroupBy(resources.Field("resource"))
  674. ret := []struct {
  675. Resource string
  676. OwnerId string
  677. ResCount int
  678. LastUpdate time.Time
  679. }{}
  680. err := q.All(&ret)
  681. if err != nil {
  682. return nil, err
  683. }
  684. result := map[string]api.ExternalResourceInfo{}
  685. for _, res := range ret {
  686. if res.ResCount <= 0 {
  687. continue
  688. }
  689. _, ok := result[res.OwnerId]
  690. if ok {
  691. result[res.OwnerId].ExtResource[res.Resource] = res.ResCount
  692. } else {
  693. result[res.OwnerId] = api.ExternalResourceInfo{
  694. ExtResource: map[string]int{
  695. res.Resource: res.ResCount,
  696. },
  697. ExtResourcesLastUpdate: res.LastUpdate,
  698. ExtResourcesNextUpdate: res.LastUpdate.Add(time.Duration(options.Options.FetchScopeResourceCountIntervalSeconds) * time.Second),
  699. }
  700. }
  701. }
  702. return result, nil
  703. }
  704. func (manager *SUserManager) FetchCustomizeColumns(
  705. ctx context.Context,
  706. userCred mcclient.TokenCredential,
  707. query jsonutils.JSONObject,
  708. objs []interface{},
  709. fields stringutils2.SSortedStrings,
  710. isList bool,
  711. ) []api.UserDetails {
  712. rows := make([]api.UserDetails, len(objs))
  713. identRows := manager.SEnabledIdentityBaseResourceManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  714. userIds := make([]string, len(rows))
  715. for i := range rows {
  716. rows[i] = api.UserDetails{
  717. EnabledIdentityBaseResourceDetails: identRows[i],
  718. }
  719. userIds[i] = objs[i].(*SUser).Id
  720. }
  721. scopeResources, err := manager.FetchScopeResources(userIds)
  722. if err != nil {
  723. log.Errorf("FetchScopeResources error: %v", err)
  724. return rows
  725. }
  726. usage, err := manager.TotalResourceCount(userIds)
  727. if err != nil {
  728. log.Errorf("TotalResourceCount error: %v", err)
  729. return rows
  730. }
  731. projects := ProjectManager.Query().SubQuery()
  732. domains := DomainManager.Query().SubQuery()
  733. subq := manager.fetchProjectUnion(userIds)
  734. q := projects.Query(
  735. projects.Field("id"),
  736. projects.Field("name"),
  737. projects.Field("domain_id"),
  738. domains.Field("name").Label("domain_name"),
  739. subq.Field("actor_id").Label("user_id"),
  740. )
  741. q = q.Join(domains, sqlchemy.Equals(projects.Field("domain_id"), domains.Field("id")))
  742. q = q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("target_id")))
  743. userProjects := []struct {
  744. api.SFetchDomainObjectWithMetadata
  745. UserId string
  746. }{}
  747. err = q.All(&userProjects)
  748. if err != nil {
  749. log.Errorf("query projects error: %v", err)
  750. return rows
  751. }
  752. metaMap := map[string]map[string]string{}
  753. if db.IsAllowList(rbacscope.ScopeProject, userCred, db.Metadata).Result.IsAllow() {
  754. projectIds := []string{}
  755. for _, p := range userProjects {
  756. projectIds = append(projectIds, p.Id)
  757. }
  758. metadata := []db.SMetadata{}
  759. q = db.Metadata.Query().Equals("obj_type", ProjectManager.Keyword()).In("obj_id", projectIds)
  760. err = q.Filter(sqlchemy.NOT(sqlchemy.Startswith(q.Field("key"), db.SYSTEM_ADMIN_PREFIX))).All(&metadata)
  761. if err != nil {
  762. log.Errorf("query metdata error: %v", err)
  763. return rows
  764. }
  765. for _, meta := range metadata {
  766. _, ok := metaMap[meta.ObjId]
  767. if !ok {
  768. metaMap[meta.ObjId] = map[string]string{}
  769. }
  770. metaMap[meta.ObjId][meta.Key] = meta.Value
  771. }
  772. }
  773. projectMap := map[string][]api.SFetchDomainObjectWithMetadata{}
  774. for _, p := range userProjects {
  775. _, ok := projectMap[p.UserId]
  776. if !ok {
  777. projectMap[p.UserId] = []api.SFetchDomainObjectWithMetadata{}
  778. }
  779. p.SFetchDomainObjectWithMetadata.Metadata = metaMap[p.Id]
  780. projectMap[p.UserId] = append(projectMap[p.UserId], p.SFetchDomainObjectWithMetadata)
  781. }
  782. groups := GroupManager.Query().SubQuery()
  783. domains = DomainManager.Query().SubQuery()
  784. ugs := UsergroupManager.Query().In("user_id", userIds).SubQuery()
  785. q = groups.Query(
  786. groups.Field("id"),
  787. groups.Field("name"),
  788. domains.Field("id").Label("domain_id"),
  789. domains.Field("name").Label("domain"),
  790. ugs.Field("user_id"),
  791. )
  792. q = q.Join(domains, sqlchemy.Equals(groups.Field("domain_id"), domains.Field("id")))
  793. q = q.Join(ugs, sqlchemy.Equals(groups.Field("id"), ugs.Field("group_id")))
  794. useGroups := []struct {
  795. api.SUserGroup
  796. UserId string
  797. }{}
  798. err = q.All(&useGroups)
  799. if err != nil {
  800. log.Errorf("query user groups error: %v", err)
  801. return rows
  802. }
  803. groupMap := map[string][]api.SUserGroup{}
  804. for _, ug := range useGroups {
  805. _, ok := groupMap[ug.UserId]
  806. if !ok {
  807. groupMap[ug.UserId] = []api.SUserGroup{}
  808. }
  809. groupMap[ug.UserId] = append(groupMap[ug.UserId], ug.SUserGroup)
  810. }
  811. userLogins := make(map[string]*SUserLogin)
  812. userLoginRows := []SUserLogin{}
  813. err = UserLoginManager.Query().In("user_id", userIds).All(&userLoginRows)
  814. if err != nil {
  815. log.Errorf("query user logins error: %v", err)
  816. return rows
  817. }
  818. for _, userLogin := range userLoginRows {
  819. userLogins[userLogin.UserId] = &userLogin
  820. }
  821. for i := range rows {
  822. rows[i].ExternalResourceInfo = scopeResources[userIds[i]]
  823. rows[i].UserUsage = usage[userIds[i]]
  824. rows[i].Projects = projectMap[userIds[i]]
  825. rows[i].Groups = groupMap[userIds[i]]
  826. if userLogin, ok := userLogins[userIds[i]]; ok {
  827. rows[i].LastActiveAt = userLogin.LastActiveAt
  828. rows[i].LastLoginIp = userLogin.LastLoginIp
  829. rows[i].LastLoginSource = userLogin.LastLoginSource
  830. }
  831. }
  832. return rows
  833. }
  834. type SUserUsageCount struct {
  835. Id string
  836. api.UserUsage
  837. }
  838. func (m *SUserManager) query(manager db.IModelManager, field string, userIds []string, filter func(*sqlchemy.SQuery) *sqlchemy.SQuery) *sqlchemy.SSubQuery {
  839. q := manager.Query()
  840. if filter != nil {
  841. q = filter(q)
  842. }
  843. key := "user_id"
  844. sq := q.SubQuery()
  845. return sq.Query(
  846. sq.Field(key),
  847. sqlchemy.COUNT(field),
  848. ).In(key, userIds).GroupBy(sq.Field(key)).SubQuery()
  849. }
  850. func (manager *SUserManager) fetchProjectUnion(userIds []string) *sqlchemy.SUnion {
  851. p1 := AssignmentManager.Query("actor_id", "target_id", "type").Equals("type", api.AssignmentUserProject).IsFalse("inherited").In("actor_id", userIds)
  852. p2Q := UsergroupManager.Query().In("user_id", userIds).SubQuery()
  853. asq := AssignmentManager.Query().IsFalse("inherited").Equals("type", api.AssignmentGroupProject).SubQuery()
  854. p2 := p2Q.Query(
  855. p2Q.Field("user_id").Label("actor_id"),
  856. asq.Field("target_id").Label("target_id"),
  857. asq.Field("type").Label("type"),
  858. ).Join(asq, sqlchemy.Equals(p2Q.Field("group_id"), asq.Field("actor_id")))
  859. return sqlchemy.Union(p1, p2)
  860. }
  861. func (manager *SUserManager) TotalResourceCount(userIds []string) (map[string]api.UserUsage, error) {
  862. // group
  863. groupSQ := manager.query(UsergroupManager, "group_cnt", userIds, nil)
  864. // credential
  865. credSQ := manager.query(CredentialManager, "cred_cnt", userIds, nil)
  866. // project
  867. sq := manager.fetchProjectUnion(userIds)
  868. projectSQ := sq.Query(
  869. sq.Field("actor_id"),
  870. sqlchemy.COUNT("project_cnt"),
  871. ).GroupBy(sq.Field("actor_id")).SubQuery()
  872. users := manager.Query().SubQuery()
  873. userQ := users.Query(
  874. sqlchemy.SUM("group_count", groupSQ.Field("group_cnt")),
  875. sqlchemy.SUM("credential_count", credSQ.Field("cred_cnt")),
  876. sqlchemy.SUM("project_count", projectSQ.Field("project_cnt")),
  877. )
  878. userQ.AppendField(userQ.Field("id"))
  879. userQ = userQ.LeftJoin(groupSQ, sqlchemy.Equals(userQ.Field("id"), groupSQ.Field("user_id")))
  880. userQ = userQ.LeftJoin(credSQ, sqlchemy.Equals(userQ.Field("id"), credSQ.Field("user_id")))
  881. userQ = userQ.LeftJoin(projectSQ, sqlchemy.Equals(userQ.Field("id"), projectSQ.Field("actor_id")))
  882. userQ = userQ.Filter(sqlchemy.In(userQ.Field("id"), userIds)).GroupBy(userQ.Field("id"))
  883. userCount := []SUserUsageCount{}
  884. err := userQ.All(&userCount)
  885. if err != nil {
  886. return nil, errors.Wrapf(err, "userQ.All")
  887. }
  888. localUsers := []SLocalUser{}
  889. err = LocalUserManager.Query().In("user_id", userIds).All(&localUsers)
  890. if err != nil {
  891. return nil, err
  892. }
  893. localUserIds := []int{}
  894. userMap := map[string]*SLocalUser{}
  895. for i := range localUsers {
  896. user := localUsers[i]
  897. userMap[user.UserId] = &user
  898. localUserIds = append(localUserIds, user.Id)
  899. }
  900. passes := make([]SPassword, 0)
  901. err = PasswordManager.Query().In("local_user_id", localUserIds).All(&passes)
  902. if err != nil {
  903. return nil, errors.Wrapf(err, "Password.Query")
  904. }
  905. passwdMap := map[int]time.Time{}
  906. for _, pass := range passes {
  907. if pass.ExpiresAt.IsZero() {
  908. continue
  909. }
  910. if _, ok := passwdMap[pass.LocalUserId]; !ok {
  911. passwdMap[pass.LocalUserId] = pass.ExpiresAt
  912. }
  913. }
  914. idpsMaps, err := fetchIdmappings(userIds, api.IdMappingEntityUser)
  915. if err != nil {
  916. return nil, errors.Wrapf(err, "fetchIdmappings")
  917. }
  918. idpMap := map[string][]api.IdpResourceInfo{}
  919. for _, uid := range userIds {
  920. if idps, ok := idpsMaps[uid]; ok {
  921. data := []api.IdpResourceInfo{}
  922. for _, idp := range idps {
  923. data = append(data, idp.IdpResourceInfo)
  924. }
  925. idpMap[uid] = data
  926. }
  927. }
  928. result := map[string]api.UserUsage{}
  929. for i := range userCount {
  930. if user, ok := userMap[userCount[i].Id]; ok {
  931. userCount[i].IsLocal = true
  932. userCount[i].FailedAuthCount = user.FailedAuthCount
  933. userCount[i].FailedAuthAt = user.FailedAuthAt
  934. if user.NeedResetPassword.IsTrue() {
  935. userCount[i].NeedResetPassword = true
  936. userCount[i].PasswordResetHint = user.ResetHint
  937. }
  938. if expire, ok := passwdMap[userMap[userCount[i].Id].Id]; ok {
  939. userCount[i].PasswordExpiresAt = expire
  940. }
  941. }
  942. userCount[i].Idps, _ = idpMap[userCount[i].Id]
  943. result[userCount[i].Id] = userCount[i].UserUsage
  944. }
  945. return result, nil
  946. }
  947. func (user *SUser) initLocalData(passwd string, skipPassCheck bool) error {
  948. localUsr, err := LocalUserManager.register(user.Id, user.DomainId, user.Name)
  949. if err != nil {
  950. return errors.Wrap(err, "register localuser")
  951. }
  952. if len(passwd) > 0 {
  953. err = PasswordManager.savePassword(localUsr.Id, passwd, user.IsSystemAccount.Bool())
  954. if err != nil {
  955. return errors.Wrap(err, "save password")
  956. }
  957. if skipPassCheck {
  958. localUsr.markNeedResetPassword(true, api.PasswordResetHintAdminReset)
  959. } else {
  960. localUsr.markNeedResetPassword(false, "")
  961. }
  962. }
  963. return nil
  964. }
  965. func (user *SUser) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
  966. user.SEnabledIdentityBaseResource.PostCreate(ctx, userCred, ownerId, query, data)
  967. // set password
  968. passwd, _ := data.GetString("password")
  969. skipPassCheck := jsonutils.QueryBoolean(data, "skip_password_complexity_check", false)
  970. err := user.initLocalData(passwd, skipPassCheck)
  971. if err != nil {
  972. log.Errorf("fail to register localUser %s", err)
  973. return
  974. }
  975. // link idp
  976. idpId, _ := data.GetString("idp_id")
  977. if len(idpId) > 0 {
  978. idpEntityId, _ := data.GetString("idp_entity_id")
  979. if len(idpEntityId) > 0 {
  980. _, err := IdmappingManager.RegisterIdMapWithId(ctx, idpId, idpEntityId, api.IdMappingEntityUser, user.Id)
  981. if err != nil {
  982. log.Errorf("IdmappingManager.RegisterIdMapWithId fail %s", err)
  983. }
  984. }
  985. }
  986. // clean user quota
  987. pendingUsage := &SIdentityQuota{
  988. SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()},
  989. User: 1,
  990. }
  991. quotas.CancelPendingUsage(ctx, userCred, pendingUsage, pendingUsage, true)
  992. }
  993. func (user *SUser) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
  994. user.SEnabledIdentityBaseResource.PostUpdate(ctx, userCred, query, data)
  995. passwd, _ := data.GetString("password")
  996. if len(passwd) > 0 {
  997. usrExt, err := UserManager.FetchUserExtended(user.Id, "", "", "")
  998. if err != nil {
  999. log.Errorf("UserManager.FetchUserExtended fail %s", err)
  1000. return
  1001. }
  1002. err = PasswordManager.savePassword(usrExt.LocalId, passwd, user.IsSystemAccount.Bool())
  1003. if err != nil {
  1004. log.Errorf("fail to set password %s", err)
  1005. return
  1006. }
  1007. localUsr, err := LocalUserManager.fetchLocalUser("", "", usrExt.LocalId)
  1008. if err != nil {
  1009. log.Errorf("Fail to fetch localUser %d: %s", usrExt.LocalId, err)
  1010. } else {
  1011. skipPassCheck := jsonutils.QueryBoolean(data, "skip_password_complexity_check", false)
  1012. if skipPassCheck {
  1013. localUsr.markNeedResetPassword(true, api.PasswordResetHintAdminReset)
  1014. } else {
  1015. localUsr.markNeedResetPassword(false, "")
  1016. }
  1017. }
  1018. logclient.AddActionLogWithContext(ctx, user, logclient.ACT_UPDATE_PASSWORD, nil, userCred, true)
  1019. }
  1020. if enabled, err := data.Bool("enabled"); err == nil {
  1021. if enabled {
  1022. err := user.clearFailedAuth()
  1023. if err != nil {
  1024. log.Errorf("clearFailedAuth %s", err)
  1025. }
  1026. } else {
  1027. batchErr := TokenCacheManager.BatchInvalidateByUserId(ctx, userCred, user.Id)
  1028. if batchErr != nil {
  1029. log.Errorf("BatchInvalidateByUserId fail %s", batchErr)
  1030. }
  1031. }
  1032. }
  1033. }
  1034. func (user *SUser) clearFailedAuth() error {
  1035. localUser, err := LocalUserManager.fetchLocalUser(user.Id, user.DomainId, 0)
  1036. if err != nil {
  1037. if err == sql.ErrNoRows {
  1038. return nil
  1039. }
  1040. return errors.Wrapf(err, "unable to fetch localUser of user %q in domain %q", user.Id, user.DomainId)
  1041. }
  1042. if err = localUser.ClearFailedAuth(); err != nil {
  1043. return errors.Wrap(err, "unable to clear failed auth")
  1044. }
  1045. return nil
  1046. }
  1047. func (user *SUser) ValidateDeleteCondition(ctx context.Context, info *api.UserDetails) error {
  1048. if user.IsAdminUser() {
  1049. return httperrors.NewForbiddenError("cannot delete system user")
  1050. }
  1051. if info == nil {
  1052. usage, err := UserManager.TotalResourceCount([]string{user.Id})
  1053. if err != nil {
  1054. return err
  1055. }
  1056. info = &api.UserDetails{}
  1057. info.UserUsage, _ = usage[user.Id]
  1058. }
  1059. if !info.IsLocal && len(info.Idps) > 0 {
  1060. for _, idp := range info.Idps {
  1061. if !idp.IsSso {
  1062. return httperrors.NewForbiddenError("cannot delete non-local non-sso user")
  1063. }
  1064. }
  1065. }
  1066. return user.SIdentityBaseResource.ValidateDeleteCondition(ctx, nil)
  1067. }
  1068. func (user *SUser) getExternalResources() (map[string]int, time.Time, error) {
  1069. return ScopeResourceManager.getScopeResource("", "", user.Id)
  1070. }
  1071. func (user *SUser) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
  1072. err := AssignmentManager.projectRemoveAllUser(ctx, userCred, user)
  1073. if err != nil {
  1074. return errors.Wrap(err, "AssignmentManager.projectRemoveAllUser")
  1075. }
  1076. err = UsergroupManager.delete(user.Id, "")
  1077. if err != nil {
  1078. return errors.Wrap(err, "UsergroupManager.delete")
  1079. }
  1080. localUser, err := LocalUserManager.delete(user.Id, user.DomainId)
  1081. if err != nil {
  1082. return errors.Wrap(err, "LocalUserManager.delete")
  1083. }
  1084. if localUser != nil {
  1085. err = PasswordManager.delete(localUser.Id)
  1086. if err != nil {
  1087. return errors.Wrap(err, "PasswordManager.delete")
  1088. }
  1089. }
  1090. {
  1091. batchErr := TokenCacheManager.BatchInvalidateByUserId(ctx, userCred, user.Id)
  1092. if batchErr != nil {
  1093. log.Errorf("BatchInvalidateByUserId fail %s", batchErr)
  1094. }
  1095. }
  1096. err = IdmappingManager.deleteByPublicId(user.Id, api.IdMappingEntityUser)
  1097. if err != nil {
  1098. return errors.Wrap(err, "IdmappingManager.deleteByPublicId")
  1099. }
  1100. return user.SEnabledIdentityBaseResource.Delete(ctx, userCred)
  1101. }
  1102. func (user *SUser) UpdateInContext(ctx context.Context, userCred mcclient.TokenCredential, ctxObjs []db.IModel, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  1103. if len(ctxObjs) != 1 {
  1104. return nil, httperrors.NewInputParameterError("not supported update context")
  1105. }
  1106. group, ok := ctxObjs[0].(*SGroup)
  1107. if !ok {
  1108. return nil, httperrors.NewInputParameterError("not supported update context %s", ctxObjs[0].Keyword())
  1109. }
  1110. if user.DomainId != group.DomainId {
  1111. return nil, httperrors.NewInputParameterError("cannot join user and group in differnt domain")
  1112. }
  1113. if group.IsReadOnly() {
  1114. return nil, httperrors.NewForbiddenError("cannot join read-only group")
  1115. }
  1116. return nil, UsergroupManager.add(ctx, userCred, user, group)
  1117. }
  1118. func (user *SUser) DeleteInContext(ctx context.Context, userCred mcclient.TokenCredential, ctxObjs []db.IModel, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  1119. if len(ctxObjs) != 1 {
  1120. return nil, httperrors.NewInputParameterError("not supported update context")
  1121. }
  1122. group, ok := ctxObjs[0].(*SGroup)
  1123. if !ok {
  1124. return nil, httperrors.NewInputParameterError("not supported update context %s", ctxObjs[0].Keyword())
  1125. }
  1126. if group.IsReadOnly() {
  1127. return nil, httperrors.NewForbiddenError("cannot leave read-only group")
  1128. }
  1129. return nil, UsergroupManager.remove(ctx, userCred, user, group)
  1130. }
  1131. func (manager *SUserManager) TraceLoginV2(ctx context.Context, token *mcclient.TokenCredentialV2) {
  1132. s := tokenV2LoginSession(token)
  1133. manager.traceLoginEvent(ctx, token, s, token.Context)
  1134. }
  1135. func (manager *SUserManager) TraceLoginV3(ctx context.Context, token *mcclient.TokenCredentialV3) {
  1136. s := tokenV3LoginSession(token)
  1137. manager.traceLoginEvent(ctx, token, s, token.Token.Context)
  1138. }
  1139. func (manager *SUserManager) traceLoginEvent(ctx context.Context, token mcclient.TokenCredential, s sLoginSession, authCtx mcclient.SAuthContext) {
  1140. usr, err := manager.fetchUserById(token.GetUserId())
  1141. if err != nil {
  1142. // very unlikely
  1143. log.Errorf("fetchUserById fail %s", err)
  1144. return
  1145. }
  1146. {
  1147. err = UserLoginManager.traceLoginEvent(ctx, usr.Id, authCtx)
  1148. if err != nil {
  1149. log.Errorf("UserLoginManager.traceLoginEvent fail %s", err)
  1150. }
  1151. }
  1152. db.OpsLog.LogEvent(usr, "auth", &s, token)
  1153. // to reduce auth event, log web console login only
  1154. if authCtx.Source == mcclient.AuthSourceWeb && token.GetProjectId() != "" {
  1155. logclient.AddActionLogWithContext(ctx, usr, logclient.ACT_AUTHENTICATE, &s, token, true)
  1156. return
  1157. }
  1158. // ignore any other auth source
  1159. }
  1160. type sLoginSession struct {
  1161. Version string
  1162. Source string
  1163. Ip string
  1164. Project string
  1165. ProjectId string
  1166. ProjectDomain string
  1167. ProjectDomainId string
  1168. Token string
  1169. }
  1170. func tokenV2LoginSession(token *mcclient.TokenCredentialV2) sLoginSession {
  1171. s := sLoginSession{}
  1172. s.Version = "v2"
  1173. s.Source = token.Context.Source
  1174. s.Ip = token.Context.Ip
  1175. s.Project = token.Token.Tenant.Name
  1176. s.ProjectId = token.Token.Tenant.Id
  1177. s.ProjectDomain = token.Token.Tenant.Domain.Name
  1178. s.ProjectDomainId = token.Token.Tenant.Domain.Id
  1179. s.Token = token.Token.Id
  1180. return s
  1181. }
  1182. func tokenV3LoginSession(token *mcclient.TokenCredentialV3) sLoginSession {
  1183. s := sLoginSession{}
  1184. s.Version = "v3"
  1185. s.Source = token.Token.Context.Source
  1186. s.Ip = token.Token.Context.Ip
  1187. s.Project = token.Token.Project.Name
  1188. s.ProjectId = token.Token.Project.Id
  1189. s.ProjectDomain = token.Token.Project.Domain.Name
  1190. s.ProjectDomainId = token.Token.Project.Domain.Id
  1191. s.Token = token.Id
  1192. return s
  1193. }
  1194. func (manager *SUserManager) NamespaceScope() rbacscope.TRbacScope {
  1195. return rbacscope.ScopeDomain
  1196. }
  1197. func (user *SUser) getIdmappings() ([]SIdmapping, error) {
  1198. return IdmappingManager.FetchEntities(user.Id, api.IdMappingEntityUser)
  1199. }
  1200. func (user *SUser) IsLocal() bool {
  1201. usr, _ := LocalUserManager.fetchLocalUser(user.Id, user.DomainId, 0)
  1202. if usr != nil {
  1203. return true
  1204. }
  1205. return false
  1206. }
  1207. func (user *SUser) LinkedWithIdp(idpId string) bool {
  1208. idmaps, _ := user.getIdmappings()
  1209. for i := range idmaps {
  1210. if idmaps[i].IdpId == idpId {
  1211. return true
  1212. }
  1213. }
  1214. return false
  1215. }
  1216. func (manager *SUserManager) FetchUsersInDomain(domainId string, excludes []string) ([]SUser, error) {
  1217. q := manager.Query().Equals("domain_id", domainId).NotIn("id", excludes)
  1218. usrs := make([]SUser, 0)
  1219. err := db.FetchModelObjects(manager, q, &usrs)
  1220. if err != nil && err != sql.ErrNoRows {
  1221. return nil, errors.Wrap(err, "db.FetchModelObjects")
  1222. }
  1223. return usrs, nil
  1224. }
  1225. func (user *SUser) UnlinkIdp(idpId string) error {
  1226. return IdmappingManager.deleteAny(idpId, api.IdMappingEntityUser, user.Id)
  1227. }
  1228. // 用户加入项目
  1229. func (user *SUser) PerformJoin(
  1230. ctx context.Context,
  1231. userCred mcclient.TokenCredential,
  1232. query jsonutils.JSONObject,
  1233. input api.SJoinProjectsInput,
  1234. ) (jsonutils.JSONObject, error) {
  1235. err := joinProjects(user, true, ctx, userCred, input)
  1236. if err != nil {
  1237. logclient.AddActionLogWithContext(ctx, user, logclient.ACT_JOIN_PROJECT, nil, userCred, false)
  1238. return nil, errors.Wrap(err, "joinProjects")
  1239. }
  1240. if input.Enabled {
  1241. db.EnabledPerformEnable(user, ctx, userCred, true)
  1242. }
  1243. logclient.AddActionLogWithContext(ctx, user, logclient.ACT_JOIN_PROJECT, nil, userCred, true)
  1244. return nil, nil
  1245. }
  1246. func joinProjects(ident db.IModel, isUser bool, ctx context.Context, userCred mcclient.TokenCredential, input api.SJoinProjectsInput) error {
  1247. err := input.Validate()
  1248. if err != nil {
  1249. return httperrors.NewInputParameterError("%v", err)
  1250. }
  1251. projects := make([]*SProject, 0)
  1252. roles := make([]*SRole, 0)
  1253. roleIds := make([]string, 0)
  1254. for i := range input.Roles {
  1255. obj, err := RoleManager.FetchByIdOrName(ctx, userCred, input.Roles[i])
  1256. if err != nil {
  1257. if err == sql.ErrNoRows {
  1258. return httperrors.NewResourceNotFoundError2(RoleManager.Keyword(), input.Roles[i])
  1259. } else {
  1260. return httperrors.NewGeneralError(err)
  1261. }
  1262. }
  1263. role := obj.(*SRole)
  1264. roles = append(roles, role)
  1265. roleIds = append(roleIds, role.Id)
  1266. }
  1267. for i := range input.Projects {
  1268. obj, err := ProjectManager.FetchByIdOrName(ctx, userCred, input.Projects[i])
  1269. if err != nil {
  1270. if err == sql.ErrNoRows {
  1271. return httperrors.NewResourceNotFoundError2(ProjectManager.Keyword(), input.Projects[i])
  1272. } else {
  1273. return httperrors.NewGeneralError(err)
  1274. }
  1275. }
  1276. project := obj.(*SProject)
  1277. err = validateJoinProject(userCred, project, roleIds)
  1278. if err != nil {
  1279. return errors.Wrapf(err, "validateJoinProject %s(%s)", project.Id, project.Name)
  1280. }
  1281. projects = append(projects, project)
  1282. }
  1283. for i := range projects {
  1284. for j := range roles {
  1285. if isUser {
  1286. err = AssignmentManager.ProjectAddUser(ctx, userCred, projects[i], ident.(*SUser), roles[j])
  1287. } else {
  1288. err = AssignmentManager.projectAddGroup(ctx, userCred, projects[i], ident.(*SGroup), roles[j])
  1289. }
  1290. if err != nil {
  1291. return httperrors.NewGeneralError(err)
  1292. }
  1293. }
  1294. }
  1295. return nil
  1296. }
  1297. // 用户退出项目
  1298. func (user *SUser) PerformLeave(
  1299. ctx context.Context,
  1300. userCred mcclient.TokenCredential,
  1301. query jsonutils.JSONObject,
  1302. input api.SLeaveProjectsInput,
  1303. ) (jsonutils.JSONObject, error) {
  1304. err := leaveProjects(user, true, ctx, userCred, input)
  1305. if err != nil {
  1306. logclient.AddActionLogWithContext(ctx, user, logclient.ACT_LEAVE_PROJECT, nil, userCred, false)
  1307. return nil, err
  1308. }
  1309. logclient.AddActionLogWithContext(ctx, user, logclient.ACT_LEAVE_PROJECT, nil, userCred, true)
  1310. return nil, nil
  1311. }
  1312. func leaveProjects(ident db.IModel, isUser bool, ctx context.Context, userCred mcclient.TokenCredential, input api.SLeaveProjectsInput) error {
  1313. for i := range input.ProjectRoles {
  1314. projObj, err := ProjectManager.FetchByIdOrName(ctx, userCred, input.ProjectRoles[i].Project)
  1315. if err != nil {
  1316. if err == sql.ErrNoRows {
  1317. return httperrors.NewResourceNotFoundError2(ProjectManager.Keyword(), input.ProjectRoles[i].Project)
  1318. } else {
  1319. return httperrors.NewGeneralError(err)
  1320. }
  1321. }
  1322. roleObj, err := RoleManager.FetchByIdOrName(ctx, userCred, input.ProjectRoles[i].Role)
  1323. if err != nil {
  1324. if err == sql.ErrNoRows {
  1325. return httperrors.NewResourceNotFoundError2(RoleManager.Keyword(), input.ProjectRoles[i].Role)
  1326. } else {
  1327. return httperrors.NewGeneralError(err)
  1328. }
  1329. }
  1330. if isUser {
  1331. err = AssignmentManager.projectRemoveUser(ctx, userCred, projObj.(*SProject), ident.(*SUser), roleObj.(*SRole))
  1332. } else {
  1333. err = AssignmentManager.projectRemoveGroup(ctx, userCred, projObj.(*SProject), ident.(*SGroup), roleObj.(*SRole))
  1334. }
  1335. if err != nil {
  1336. return httperrors.NewGeneralError(err)
  1337. }
  1338. }
  1339. return nil
  1340. }
  1341. func (manager *SUserManager) LockUser(uid string, reason string) error {
  1342. usrObj, err := manager.FetchById(uid)
  1343. if err != nil {
  1344. return errors.Wrapf(err, "manager.FetchById %s", uid)
  1345. }
  1346. usr := usrObj.(*SUser)
  1347. _, err = db.Update(usr, func() error {
  1348. usr.Enabled = tristate.False
  1349. return nil
  1350. })
  1351. if err != nil {
  1352. return errors.Wrap(err, "Update")
  1353. }
  1354. db.OpsLog.LogEvent(usr, db.ACT_DISABLE, reason, GetDefaultAdminCred())
  1355. logclient.AddSimpleActionLog(usr, logclient.ACT_DISABLE, reason, GetDefaultAdminCred(), false)
  1356. return nil
  1357. }
  1358. func (manager *SUserManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man db.FilterByOwnerProvider, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
  1359. log.Debugf("owner: %s scope %s", jsonutils.Marshal(owner), scope)
  1360. if owner != nil && scope == rbacscope.ScopeProject {
  1361. // if user has project level privilege, returns all users in user's project
  1362. subq := AssignmentManager.fetchProjectUserIdsQuery(owner.GetProjectId())
  1363. q = q.In("id", subq.SubQuery())
  1364. return q
  1365. }
  1366. return manager.SEnabledIdentityBaseResourceManager.FilterByOwner(ctx, q, man, userCred, owner, scope)
  1367. }
  1368. func (user *SUser) GetUsages() []db.IUsage {
  1369. usage := SIdentityQuota{
  1370. SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: user.DomainId},
  1371. User: 1,
  1372. }
  1373. return []db.IUsage{
  1374. &usage,
  1375. }
  1376. }
  1377. // 用户和IDP的指定entityId关联
  1378. func (user *SUser) PerformLinkIdp(
  1379. ctx context.Context,
  1380. userCred mcclient.TokenCredential,
  1381. query jsonutils.JSONObject,
  1382. input api.UserLinkIdpInput,
  1383. ) (jsonutils.JSONObject, error) {
  1384. idp, err := IdentityProviderManager.FetchIdentityProviderById(input.IdpId)
  1385. if err != nil {
  1386. if errors.Cause(err) == sql.ErrNoRows {
  1387. return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", IdentityProviderManager.Keyword(), input.IdpId)
  1388. } else {
  1389. return nil, errors.Wrap(err, "IdentityProviderManager.FetchIdentityProviderById")
  1390. }
  1391. }
  1392. // check accessibility
  1393. if (len(idp.DomainId) > 0 && idp.DomainId != user.DomainId) || (len(idp.TargetDomainId) > 0 && idp.TargetDomainId != user.DomainId) {
  1394. return nil, errors.Wrap(httperrors.ErrForbidden, "identity domain not accessible")
  1395. } else if len(idp.DomainId) == 0 && len(idp.TargetDomainId) == 0 && idp.AutoCreateUser.IsTrue() {
  1396. }
  1397. _, err = IdmappingManager.RegisterIdMapWithId(ctx, input.IdpId, input.IdpEntityId, api.IdMappingEntityUser, user.Id)
  1398. if err != nil {
  1399. return nil, errors.Wrap(err, "IdmappingManager.RegisterIdMapWithId")
  1400. }
  1401. return nil, nil
  1402. }
  1403. // 用户和IDP的指定entityId解除关联
  1404. func (user *SUser) PerformUnlinkIdp(
  1405. ctx context.Context,
  1406. userCred mcclient.TokenCredential,
  1407. query jsonutils.JSONObject,
  1408. input api.UserUnlinkIdpInput,
  1409. ) (jsonutils.JSONObject, error) {
  1410. err := IdmappingManager.deleteAny(input.IdpId, api.IdMappingEntityUser, user.Id)
  1411. if err != nil {
  1412. return nil, errors.Wrap(err, "IdmappingManager.deleteAny")
  1413. }
  1414. return nil, nil
  1415. }
  1416. func GetUserLangForKeyStone(uids []string) (map[string]string, error) {
  1417. simpleUsers := make([]struct {
  1418. Id string
  1419. Lang string
  1420. }, 0, len(uids))
  1421. q := UserManager.Query()
  1422. if len(uids) == 0 {
  1423. return nil, nil
  1424. } else if len(uids) == 1 {
  1425. q = q.Equals("id", uids[0])
  1426. } else {
  1427. q = q.In("id", uids)
  1428. }
  1429. err := q.All(&simpleUsers)
  1430. if err != nil {
  1431. return nil, err
  1432. }
  1433. ret := make(map[string]string, len(simpleUsers))
  1434. for i := range simpleUsers {
  1435. ret[simpleUsers[i].Id] = simpleUsers[i].Lang
  1436. }
  1437. return ret, nil
  1438. }
  1439. // 用户加入项目
  1440. func (user *SUser) PerformResetCredentials(
  1441. ctx context.Context,
  1442. userCred mcclient.TokenCredential,
  1443. query jsonutils.JSONObject,
  1444. input api.ResetCredentialInput,
  1445. ) (jsonutils.JSONObject, error) {
  1446. err := CredentialManager.DeleteAll(ctx, userCred, user.Id, input.Type)
  1447. if err != nil {
  1448. return nil, errors.Wrapf(err, "DeleteAll %s", input.Type)
  1449. }
  1450. if input.Type == api.TOTP_TYPE {
  1451. err := CredentialManager.DeleteAll(ctx, userCred, user.Id, api.RECOVERY_SECRETS_TYPE)
  1452. if err != nil {
  1453. return nil, errors.Wrapf(err, "DeleteAll %s", api.RECOVERY_SECRETS_TYPE)
  1454. }
  1455. }
  1456. logclient.AddActionLogWithContext(ctx, user, logclient.ACT_RESET_CREDENTIAL, nil, userCred, true)
  1457. return nil, nil
  1458. }
  1459. func (user *SUser) PerformEnable(
  1460. ctx context.Context,
  1461. userCred mcclient.TokenCredential,
  1462. query jsonutils.JSONObject,
  1463. input apis.PerformEnableInput,
  1464. ) (jsonutils.JSONObject, error) {
  1465. err := user.clearFailedAuth()
  1466. if err != nil {
  1467. log.Errorf("clearFailedAuth %s", err)
  1468. }
  1469. return user.SEnabledIdentityBaseResource.PerformEnable(ctx, userCred, query, input)
  1470. }
  1471. func (user *SUser) PerformDisable(
  1472. ctx context.Context,
  1473. userCred mcclient.TokenCredential,
  1474. query jsonutils.JSONObject,
  1475. input apis.PerformDisableInput,
  1476. ) (jsonutils.JSONObject, error) {
  1477. _, err := user.SEnabledIdentityBaseResource.PerformDisable(ctx, userCred, query, input)
  1478. if err != nil {
  1479. return nil, errors.Wrap(err, "SEnabledIdentityBaseResource.PerformDisable")
  1480. }
  1481. {
  1482. batchErr := TokenCacheManager.BatchInvalidateByUserId(ctx, userCred, user.Id)
  1483. if batchErr != nil {
  1484. log.Errorf("BatchInvalidateByUserId fail %s", batchErr)
  1485. }
  1486. }
  1487. return nil, nil
  1488. }