user.rb 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221
  1. # Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
  2. class User < ApplicationModel
  3. include CanBeAuthorized
  4. include CanBeImported
  5. include HasActivityStreamLog
  6. include ChecksClientNotification
  7. include HasHistory
  8. include HasSearchIndexBackend
  9. include CanCsvImport
  10. include ChecksHtmlSanitized
  11. include HasGroups
  12. include HasRoles
  13. include HasObjectManagerAttributes
  14. include HasTaskbars
  15. include User::Assets
  16. include User::Avatar
  17. include User::Search
  18. include User::SearchIndex
  19. include User::TouchesOrganization
  20. include User::PerformsGeoLookup
  21. include User::UpdatesTicketOrganization
  22. include HasTransactionDispatcher
  23. has_and_belongs_to_many :organizations, after_add: :cache_update, after_remove: :cache_update, class_name: 'Organization'
  24. has_and_belongs_to_many :overviews, dependent: :nullify
  25. has_many :tokens, after_add: :cache_update, after_remove: :cache_update, dependent: :destroy
  26. has_many :authorizations, after_add: :cache_update, after_remove: :cache_update, dependent: :destroy
  27. has_many :online_notifications, dependent: :destroy
  28. has_many :taskbars, dependent: :destroy
  29. has_many :user_devices, dependent: :destroy
  30. has_one :chat_agent_created_by, class_name: 'Chat::Agent', foreign_key: :created_by_id, dependent: :destroy, inverse_of: :created_by
  31. has_one :chat_agent_updated_by, class_name: 'Chat::Agent', foreign_key: :updated_by_id, dependent: :destroy, inverse_of: :updated_by
  32. has_many :chat_sessions, class_name: 'Chat::Session', dependent: :destroy
  33. has_many :karma_user, class_name: 'Karma::User', dependent: :destroy
  34. has_many :mentions, dependent: :destroy
  35. has_many :karma_activity_logs, class_name: 'Karma::ActivityLog', dependent: :destroy
  36. has_many :cti_caller_ids, class_name: 'Cti::CallerId', dependent: :destroy
  37. has_many :customer_tickets, class_name: 'Ticket', foreign_key: :customer_id, dependent: :destroy, inverse_of: :customer
  38. has_many :owner_tickets, class_name: 'Ticket', foreign_key: :owner_id, inverse_of: :owner
  39. has_many :created_recent_views, class_name: 'RecentView', foreign_key: :created_by_id, dependent: :destroy, inverse_of: :created_by
  40. has_many :permissions, -> { where(roles: { active: true }, active: true) }, through: :roles
  41. has_many :data_privacy_tasks, as: :deletable
  42. belongs_to :organization, inverse_of: :members, optional: true
  43. before_validation :check_name, :check_email, :check_login, :ensure_password, :ensure_roles
  44. before_validation :check_mail_delivery_failed, on: :update
  45. before_create :check_preferences_default, :validate_preferences, :validate_ooo, :domain_based_assignment, :set_locale
  46. before_update :check_preferences_default, :validate_preferences, :validate_ooo, :reset_login_failed_after_password_change, :validate_agent_limit_by_attributes, :last_admin_check_by_attribute
  47. before_destroy :destroy_longer_required_objects, :destroy_move_dependency_ownership
  48. after_commit :update_caller_id
  49. validate :ensure_identifier, :ensure_email
  50. validate :ensure_uniq_email, unless: :skip_ensure_uniq_email
  51. # workflow checks should run after before_create and before_update callbacks
  52. include ChecksCoreWorkflow
  53. store :preferences
  54. association_attributes_ignored :online_notifications, :templates, :taskbars, :user_devices, :chat_sessions, :karma_activity_logs, :cti_caller_ids, :text_modules, :customer_tickets, :owner_tickets, :created_recent_views, :chat_agents, :data_privacy_tasks, :overviews, :mentions
  55. activity_stream_permission 'admin.user'
  56. activity_stream_attributes_ignored :last_login,
  57. :login_failed,
  58. :image,
  59. :image_source,
  60. :preferences
  61. association_attributes_ignored :permissions
  62. history_attributes_ignored :password,
  63. :last_login,
  64. :image,
  65. :image_source,
  66. :preferences
  67. search_index_attributes_ignored :password,
  68. :image,
  69. :image_source,
  70. :source,
  71. :login_failed
  72. csv_object_ids_ignored 1
  73. csv_attributes_ignored :password,
  74. :login_failed,
  75. :source,
  76. :image_source,
  77. :image,
  78. :authorizations,
  79. :organizations,
  80. :groups,
  81. :user_groups
  82. sanitized_html :note
  83. def ignore_search_indexing?(_action)
  84. # ignore internal user
  85. return true if id == 1
  86. false
  87. end
  88. =begin
  89. fullname of user
  90. user = User.find(123)
  91. result = user.fullname
  92. returns
  93. result = "Bob Smith"
  94. =end
  95. def fullname
  96. name = ''
  97. if firstname.present?
  98. name = firstname
  99. end
  100. if lastname.present?
  101. if name != ''
  102. name += ' '
  103. end
  104. name += lastname
  105. end
  106. if name.blank? && email.present?
  107. name = email
  108. end
  109. name
  110. end
  111. =begin
  112. longname of user
  113. user = User.find(123)
  114. result = user.longname
  115. returns
  116. result = "Bob Smith"
  117. or with org
  118. result = "Bob Smith (Org ABC)"
  119. =end
  120. def longname
  121. name = fullname
  122. if organization_id
  123. organization = Organization.lookup(id: organization_id)
  124. if organization
  125. name += " (#{organization.name})"
  126. end
  127. end
  128. name
  129. end
  130. =begin
  131. check if user is in role
  132. user = User.find(123)
  133. result = user.role?('Customer')
  134. result = user.role?(['Agent', 'Admin'])
  135. returns
  136. result = true|false
  137. =end
  138. def role?(role_name)
  139. roles.where(name: role_name).any?
  140. end
  141. =begin
  142. check if user is in role
  143. user = User.find(123)
  144. result = user.out_of_office?
  145. returns
  146. result = true|false
  147. =end
  148. def out_of_office?
  149. return false if out_of_office != true
  150. return false if out_of_office_start_at.blank?
  151. return false if out_of_office_end_at.blank?
  152. Time.use_zone(Setting.get('timezone_default').presence) do
  153. start = out_of_office_start_at.beginning_of_day
  154. finish = out_of_office_end_at.end_of_day
  155. Time.zone.now.between? start, finish
  156. end
  157. end
  158. =begin
  159. check if user is in role
  160. user = User.find(123)
  161. result = user.out_of_office_agent
  162. returns
  163. result = user_model
  164. =end
  165. def out_of_office_agent(loop_user_ids: [], stack_depth: 10)
  166. return if !out_of_office?
  167. return if out_of_office_replacement_id.blank?
  168. if stack_depth.zero?
  169. Rails.logger.warn("Found more than 10 replacement levels for agent #{self}.")
  170. return self
  171. end
  172. user = User.find_by(id: out_of_office_replacement_id)
  173. # stop if users are occuring multiple times to prevent endless loops
  174. return user if loop_user_ids.include?(out_of_office_replacement_id)
  175. loop_user_ids |= [out_of_office_replacement_id]
  176. ooo_agent = user.out_of_office_agent(loop_user_ids: loop_user_ids, stack_depth: stack_depth - 1)
  177. return ooo_agent if ooo_agent.present?
  178. user
  179. end
  180. =begin
  181. gets users where user is replacement
  182. user = User.find(123)
  183. result = user.out_of_office_agent_of
  184. returns
  185. result = [user_model1, user_model2]
  186. =end
  187. def out_of_office_agent_of
  188. User.where(id: out_of_office_agent_of_recursive(user_id: id))
  189. end
  190. def out_of_office_agent_of_recursive(user_id:, result: [])
  191. User.where(active: true, out_of_office: true, out_of_office_replacement_id: user_id).where('out_of_office_start_at <= ? AND out_of_office_end_at >= ?', Time.zone.today, Time.zone.today).each do |user|
  192. # stop if users are occuring multiple times to prevent endless loops
  193. break if result.include?(user.id)
  194. result |= [user.id]
  195. result |= out_of_office_agent_of_recursive(user_id: user.id, result: result)
  196. end
  197. result
  198. end
  199. =begin
  200. get users activity stream
  201. user = User.find(123)
  202. result = user.activity_stream(20)
  203. returns
  204. result = [
  205. {
  206. id: 2,
  207. o_id: 2,
  208. created_by_id: 3,
  209. created_at: '2013-09-28 00:57:21',
  210. object: "User",
  211. type: "created",
  212. },
  213. {
  214. id: 2,
  215. o_id: 2,
  216. created_by_id: 3,
  217. created_at: '2013-09-28 00:59:21',
  218. object: "User",
  219. type: "updated",
  220. },
  221. ]
  222. =end
  223. def activity_stream(limit, fulldata = false)
  224. stream = ActivityStream.list(self, limit)
  225. return stream if !fulldata
  226. # get related objects
  227. assets = {}
  228. stream.each do |item|
  229. assets = item.assets(assets)
  230. end
  231. {
  232. stream: stream,
  233. assets: assets,
  234. }
  235. end
  236. =begin
  237. tries to find the matching instance by the given identifier. Currently email and login is supported.
  238. user = User.indentify('User123')
  239. # or
  240. user = User.indentify('user-123@example.com')
  241. returns
  242. # User instance
  243. user.login # 'user123'
  244. =end
  245. def self.identify(identifier)
  246. return if identifier.blank?
  247. # try to find user based on login
  248. user = User.find_by(login: identifier.downcase)
  249. return user if user
  250. # try second lookup with email
  251. User.find_by(email: identifier.downcase)
  252. end
  253. =begin
  254. create user from from omni auth hash
  255. result = User.create_from_hash!(hash)
  256. returns
  257. result = user_model # user model if create was successfully
  258. =end
  259. def self.create_from_hash!(hash)
  260. url = ''
  261. hash['info']['urls']&.each_value do |local_url|
  262. next if local_url.blank?
  263. url = local_url
  264. end
  265. begin
  266. data = {
  267. login: hash['info']['nickname'] || hash['uid'],
  268. firstname: hash['info']['name'] || hash['info']['display_name'],
  269. email: hash['info']['email'],
  270. image_source: hash['info']['image'],
  271. web: url,
  272. address: hash['info']['location'],
  273. note: hash['info']['description'],
  274. source: hash['provider'],
  275. role_ids: Role.signup_role_ids,
  276. updated_by_id: 1,
  277. created_by_id: 1,
  278. }
  279. if hash['info']['first_name'].present? && hash['info']['last_name'].present?
  280. data[:firstname] = hash['info']['first_name']
  281. data[:lastname] = hash['info']['last_name']
  282. end
  283. create!(data)
  284. rescue => e
  285. logger.error e
  286. raise Exceptions::UnprocessableEntity, e.message
  287. end
  288. end
  289. =begin
  290. returns all accessable permission ids of user
  291. user = User.find(123)
  292. user.permissions_with_child_ids
  293. returns
  294. [permission1_id, permission2_id, permission3_id]
  295. =end
  296. def permissions_with_child_ids
  297. permissions_with_child_elements.pluck(:id)
  298. end
  299. =begin
  300. returns all accessable permission names of user
  301. user = User.find(123)
  302. user.permissions_with_child_names
  303. returns
  304. [permission1_name, permission2_name, permission3_name]
  305. =end
  306. def permissions_with_child_names
  307. permissions_with_child_elements.pluck(:name)
  308. end
  309. =begin
  310. get all users with permission
  311. users = User.with_permissions('ticket.agent')
  312. get all users with permission "admin.session" or "ticket.agent"
  313. users = User.with_permissions(['admin.session', 'ticket.agent'])
  314. returns
  315. [user1, user2, ...]
  316. =end
  317. def self.with_permissions(keys)
  318. if keys.class != Array
  319. keys = [keys]
  320. end
  321. total_role_ids = []
  322. permission_ids = []
  323. keys.each do |key|
  324. role_ids = []
  325. ::Permission.with_parents(key).each do |local_key|
  326. permission = ::Permission.lookup(name: local_key)
  327. next if !permission
  328. permission_ids.push permission.id
  329. end
  330. next if permission_ids.blank?
  331. Role.joins(:permissions_roles).joins(:permissions).where('permissions_roles.permission_id IN (?) AND roles.active = ? AND permissions.active = ?', permission_ids, true, true).distinct.pluck(:id).each do |role_id|
  332. role_ids.push role_id
  333. end
  334. total_role_ids.push role_ids
  335. end
  336. return [] if total_role_ids.blank?
  337. condition = ''
  338. total_role_ids.each do |_role_ids|
  339. if condition != ''
  340. condition += ' OR '
  341. end
  342. condition += 'roles_users.role_id IN (?)'
  343. end
  344. User.joins(:roles_users).where("(#{condition}) AND users.active = ?", *total_role_ids, true).distinct.order(:id)
  345. end
  346. =begin
  347. generate new token for reset password
  348. result = User.password_reset_new_token(username)
  349. returns
  350. result = {
  351. token: token,
  352. user: user,
  353. }
  354. =end
  355. def self.password_reset_new_token(username)
  356. return if username.blank?
  357. # try to find user based on login
  358. user = User.find_by(login: username.downcase.strip, active: true)
  359. # try second lookup with email
  360. user ||= User.find_by(email: username.downcase.strip, active: true)
  361. return if !user || !user.email
  362. # Discard any possible previous tokens for safety reasons.
  363. Token.where(action: 'PasswordReset', user_id: user.id).destroy_all
  364. {
  365. token: Token.create(action: 'PasswordReset', user_id: user.id, persistent: false),
  366. user: user,
  367. }
  368. end
  369. =begin
  370. returns the User instance for a given password token if found
  371. result = User.by_reset_token(token)
  372. returns
  373. result = user_model # user_model if token was verified
  374. =end
  375. def self.by_reset_token(token)
  376. Token.check(action: 'PasswordReset', name: token)
  377. end
  378. =begin
  379. reset password with token and set new password
  380. result = User.password_reset_via_token(token,password)
  381. returns
  382. result = user_model # user_model if token was verified
  383. =end
  384. def self.password_reset_via_token(token, password)
  385. # check token
  386. user = by_reset_token(token)
  387. return if !user
  388. # reset password
  389. user.update!(password: password, verified: true)
  390. # delete token
  391. Token.find_by(action: 'PasswordReset', name: token).destroy
  392. user
  393. end
  394. =begin
  395. update last login date and reset login_failed (is automatically done by auth and sso backend)
  396. user = User.find(123)
  397. result = user.update_last_login
  398. returns
  399. result = new_user_model
  400. =end
  401. def update_last_login
  402. # reduce DB/ES load by updating last_login every 10 minutes only
  403. if !last_login || last_login < 10.minutes.ago
  404. self.last_login = Time.zone.now
  405. end
  406. # reset login failed
  407. self.login_failed = 0
  408. save
  409. end
  410. =begin
  411. generate new token for signup
  412. result = User.signup_new_token(user) # or email
  413. returns
  414. result = {
  415. token: token,
  416. user: user,
  417. }
  418. =end
  419. def self.signup_new_token(user)
  420. return if !user
  421. return if !user.email
  422. # generate token
  423. token = Token.create(action: 'Signup', user_id: user.id)
  424. {
  425. token: token,
  426. user: user,
  427. }
  428. end
  429. =begin
  430. verify signup with token
  431. result = User.signup_verify_via_token(token, user)
  432. returns
  433. result = user_model # user_model if token was verified
  434. =end
  435. def self.signup_verify_via_token(token, user = nil)
  436. # check token
  437. local_user = Token.check(action: 'Signup', name: token)
  438. return if !local_user
  439. # if requested user is different to current user
  440. return if user && local_user.id != user.id
  441. # set verified
  442. local_user.update!(verified: true)
  443. # delete token
  444. Token.find_by(action: 'Signup', name: token).destroy
  445. local_user
  446. end
  447. =begin
  448. merge two users to one
  449. user = User.find(123)
  450. result = user.merge(user_id_of_duplicate_user)
  451. returns
  452. result = new_user_model
  453. =end
  454. def merge(user_id_of_duplicate_user)
  455. # Raise an exception if the user is not found (?)
  456. #
  457. # (This line used to contain a useless variable assignment,
  458. # and was changed to satisfy the linter.
  459. # We're not certain of its original intention,
  460. # so the User.find call has been kept
  461. # to prevent any unexpected regressions.)
  462. User.find(user_id_of_duplicate_user)
  463. # merge missing attributes
  464. Models.merge('User', id, user_id_of_duplicate_user)
  465. true
  466. end
  467. =begin
  468. list of active users in role
  469. result = User.of_role('Agent', group_ids)
  470. result = User.of_role(['Agent', 'Admin'])
  471. returns
  472. result = [user1, user2]
  473. =end
  474. def self.of_role(role, group_ids = nil)
  475. roles_ids = Role.where(active: true, name: role).map(&:id)
  476. if !group_ids
  477. return User.where(active: true).joins(:roles_users).where('roles_users.role_id' => roles_ids).order('users.updated_at DESC')
  478. end
  479. User.where(active: true)
  480. .joins(:roles_users)
  481. .joins(:users_groups)
  482. .where('roles_users.role_id IN (?) AND users_groups.group_ids IN (?)', roles_ids, group_ids).order('users.updated_at DESC')
  483. end
  484. =begin
  485. update/sync default preferences of users with dedicated permissions
  486. result = User.update_default_preferences_by_permission('ticket.agent', force)
  487. returns
  488. result = true # false
  489. =end
  490. def self.update_default_preferences_by_permission(permission_name, force = false)
  491. permission = ::Permission.lookup(name: permission_name)
  492. return if !permission
  493. default = Rails.configuration.preferences_default_by_permission
  494. return false if !default
  495. default.deep_stringify_keys!
  496. User.with_permissions(permission.name).each do |user|
  497. next if !default[permission.name]
  498. has_changed = false
  499. default[permission.name].each do |key, value|
  500. next if !force && user.preferences[key]
  501. has_changed = true
  502. user.preferences[key] = value
  503. end
  504. if has_changed
  505. user.save!
  506. end
  507. end
  508. true
  509. end
  510. =begin
  511. update/sync default preferences of users in a dedicated role
  512. result = User.update_default_preferences_by_role('Agent', force)
  513. returns
  514. result = true # false
  515. =end
  516. def self.update_default_preferences_by_role(role_name, force = false)
  517. role = Role.lookup(name: role_name)
  518. return if !role
  519. default = Rails.configuration.preferences_default_by_permission
  520. return false if !default
  521. default.deep_stringify_keys!
  522. role.permissions.each do |permission|
  523. User.update_default_preferences_by_permission(permission.name, force)
  524. end
  525. true
  526. end
  527. def check_notifications(other, should_save = true)
  528. default = Rails.configuration.preferences_default_by_permission
  529. return if !default
  530. default.deep_stringify_keys!
  531. has_changed = false
  532. other.permissions.each do |permission|
  533. next if !default[permission.name]
  534. default[permission.name].each do |key, value|
  535. next if preferences[key]
  536. preferences[key] = value
  537. has_changed = true
  538. end
  539. end
  540. return true if !has_changed
  541. if id && should_save
  542. save!
  543. return true
  544. end
  545. @preferences_default = preferences
  546. true
  547. end
  548. def check_preferences_default
  549. if @preferences_default.blank? && id
  550. roles.each do |role|
  551. check_notifications(role, false)
  552. end
  553. end
  554. return if @preferences_default.blank?
  555. preferences_tmp = @preferences_default.merge(preferences)
  556. self.preferences = preferences_tmp
  557. @preferences_default = nil
  558. true
  559. end
  560. =begin
  561. try to find correct name
  562. [firstname, lastname] = User.name_guess('Some Name', 'some.name@example.com')
  563. =end
  564. def self.name_guess(string, email = nil)
  565. return if string.blank? && email.blank?
  566. string.strip!
  567. firstname = ''
  568. lastname = ''
  569. # "Lastname, Firstname"
  570. if string.match?(',')
  571. name = string.split(', ', 2)
  572. if name.count == 2
  573. if name[0].present?
  574. lastname = name[0].strip
  575. end
  576. if name[1].present?
  577. firstname = name[1].strip
  578. end
  579. return [firstname, lastname] if firstname.present? || lastname.present?
  580. end
  581. end
  582. # "Firstname Lastname"
  583. if string =~ %r{^(((Dr\.|Prof\.)[[:space:]]|).+?)[[:space:]](.+?)$}i
  584. if $1.present?
  585. firstname = $1.strip
  586. end
  587. if $4.present?
  588. lastname = $4.strip
  589. end
  590. return [firstname, lastname] if firstname.present? || lastname.present?
  591. end
  592. # -no name- "firstname.lastname@example.com"
  593. if string.blank? && email.present?
  594. scan = email.scan(%r{^(.+?)\.(.+?)@.+?$})
  595. if scan[0].present?
  596. if scan[0][0].present?
  597. firstname = scan[0][0].strip
  598. end
  599. if scan[0][1].present?
  600. lastname = scan[0][1].strip
  601. end
  602. return [firstname, lastname] if firstname.present? || lastname.present?
  603. end
  604. end
  605. nil
  606. end
  607. def no_name?
  608. firstname.blank? && lastname.blank?
  609. end
  610. # get locale identifier of user or system if user's own is not set
  611. def locale
  612. preferences.fetch(:locale) { Locale.default }
  613. end
  614. attr_accessor :skip_ensure_uniq_email
  615. private
  616. def check_name
  617. firstname&.strip!
  618. lastname&.strip!
  619. return if firstname.present? && lastname.present?
  620. if (firstname.blank? && lastname.present?) || (firstname.present? && lastname.blank?)
  621. used_name = firstname.presence || lastname
  622. (local_firstname, local_lastname) = User.name_guess(used_name, email)
  623. elsif firstname.blank? && lastname.blank? && email.present?
  624. (local_firstname, local_lastname) = User.name_guess('', email)
  625. end
  626. check_name_apply(:firstname, local_firstname)
  627. check_name_apply(:lastname, local_lastname)
  628. end
  629. def check_name_apply(identifier, input)
  630. self[identifier] = input if input.present?
  631. self[identifier].capitalize! if self[identifier]&.match? %r{^([[:upper:]]+|[[:lower:]]+)$}
  632. end
  633. def check_email
  634. return if Setting.get('import_mode')
  635. return if email.blank?
  636. self.email = email.downcase.strip
  637. end
  638. def ensure_email
  639. return if email.blank?
  640. return if id == 1
  641. email_address_validation = EmailAddressValidation.new(email)
  642. return if email_address_validation.valid_format?
  643. errors.add :base, "Invalid email '#{email}'"
  644. end
  645. def check_login
  646. # use email as login if not given
  647. if login.blank?
  648. self.login = email
  649. end
  650. # if email has changed, login is old email, change also login
  651. if email_changed? && email_was == login
  652. self.login = email
  653. end
  654. # generate auto login
  655. if login.blank?
  656. self.login = "auto-#{SecureRandom.uuid}"
  657. end
  658. # check if login already exists
  659. base_login = login.downcase.strip
  660. alternatives = [nil] + Array(1..20) + [ SecureRandom.uuid ]
  661. alternatives.each do |suffix|
  662. self.login = "#{base_login}#{suffix}"
  663. exists = User.find_by(login: login)
  664. return true if !exists || exists.id == id
  665. end
  666. raise Exceptions::UnprocessableEntity, "Invalid user login generation for login #{login}!"
  667. end
  668. def check_mail_delivery_failed
  669. return if email_change.blank?
  670. preferences.delete(:mail_delivery_failed)
  671. end
  672. def ensure_roles
  673. return if role_ids.present?
  674. self.role_ids = Role.signup_role_ids
  675. end
  676. def ensure_identifier
  677. return if login.present? && !login.start_with?('auto-')
  678. return if [email, firstname, lastname, phone].any?(&:present?)
  679. errors.add :base, __('At least one identifier (firstname, lastname, phone or email) for user is required.')
  680. end
  681. def ensure_uniq_email
  682. return if Setting.get('user_email_multiple_use')
  683. return if Setting.get('import_mode')
  684. return if email.blank?
  685. return if !email_changed?
  686. return if !User.exists?(email: email.downcase.strip)
  687. errors.add :base, "Email address '#{email.downcase.strip}' is already used for other user."
  688. end
  689. def permissions_with_child_elements
  690. where = ''
  691. where_bind = [true]
  692. permissions.pluck(:name).each do |permission_name|
  693. where += ' OR ' if where != ''
  694. where += 'permissions.name = ? OR permissions.name LIKE ?'
  695. where_bind.push permission_name
  696. where_bind.push "#{permission_name}.%"
  697. end
  698. return [] if where == ''
  699. ::Permission.where("permissions.active = ? AND (#{where})", *where_bind)
  700. end
  701. def validate_roles(role)
  702. return true if !role_ids # we need role_ids for checking in role_ids below, in this method
  703. return true if role.preferences[:not].blank?
  704. role.preferences[:not].each do |local_role_name|
  705. local_role = Role.lookup(name: local_role_name)
  706. next if !local_role
  707. next if role_ids.exclude?(local_role.id)
  708. raise "Role #{role.name} conflicts with #{local_role.name}"
  709. end
  710. true
  711. end
  712. def validate_ooo
  713. return true if out_of_office != true
  714. raise Exceptions::UnprocessableEntity, 'out of office start is required' if out_of_office_start_at.blank?
  715. raise Exceptions::UnprocessableEntity, 'out of office end is required' if out_of_office_end_at.blank?
  716. raise Exceptions::UnprocessableEntity, 'out of office end is before start' if out_of_office_start_at > out_of_office_end_at
  717. raise Exceptions::UnprocessableEntity, 'out of office replacement user is required' if out_of_office_replacement_id.blank?
  718. raise Exceptions::UnprocessableEntity, 'out of office no such replacement user' if !User.exists?(id: out_of_office_replacement_id)
  719. true
  720. end
  721. def validate_preferences
  722. return true if !changes
  723. return true if !changes['preferences']
  724. return true if preferences.blank?
  725. return true if !preferences[:notification_sound]
  726. return true if !preferences[:notification_sound][:enabled]
  727. case preferences[:notification_sound][:enabled]
  728. when 'true'
  729. preferences[:notification_sound][:enabled] = true
  730. when 'false'
  731. preferences[:notification_sound][:enabled] = false
  732. end
  733. class_name = preferences[:notification_sound][:enabled].class.to_s
  734. raise Exceptions::UnprocessableEntity, "preferences.notification_sound.enabled needs to be an boolean, but it was a #{class_name}" if class_name != 'TrueClass' && class_name != 'FalseClass'
  735. true
  736. end
  737. =begin
  738. checks if the current user is the last one with admin permissions.
  739. Raises
  740. raise 'At least one user need to have admin permissions'
  741. =end
  742. def last_admin_check_by_attribute
  743. return true if !will_save_change_to_attribute?('active')
  744. return true if active != false
  745. return true if !permissions?(['admin', 'admin.user'])
  746. raise Exceptions::UnprocessableEntity, __('At least one user needs to have admin permissions.') if last_admin_check_admin_count < 1
  747. true
  748. end
  749. def last_admin_check_by_role(role)
  750. return true if Setting.get('import_mode')
  751. return true if !role.with_permission?(['admin', 'admin.user'])
  752. raise Exceptions::UnprocessableEntity, __('At least one user needs to have admin permissions.') if last_admin_check_admin_count < 1
  753. true
  754. end
  755. def last_admin_check_admin_count
  756. admin_role_ids = Role.joins(:permissions).where(permissions: { name: ['admin', 'admin.user'], active: true }, roles: { active: true }).pluck(:id)
  757. User.joins(:roles).where(roles: { id: admin_role_ids }, users: { active: true }).distinct.count - 1
  758. end
  759. def validate_agent_limit_by_attributes
  760. return true if Setting.get('system_agent_limit').blank?
  761. return true if !will_save_change_to_attribute?('active')
  762. return true if active != true
  763. return true if !permissions?('ticket.agent')
  764. ticket_agent_role_ids = Role.joins(:permissions).where(permissions: { name: 'ticket.agent', active: true }, roles: { active: true }).pluck(:id)
  765. count = User.joins(:roles).where(roles: { id: ticket_agent_role_ids }, users: { active: true }).distinct.count + 1
  766. raise Exceptions::UnprocessableEntity, __('Agent limit exceeded, please check your account settings.') if count > Setting.get('system_agent_limit').to_i
  767. true
  768. end
  769. def validate_agent_limit_by_role(role)
  770. return true if Setting.get('system_agent_limit').blank?
  771. return true if active != true
  772. return true if role.active != true
  773. return true if !role.with_permission?('ticket.agent')
  774. ticket_agent_role_ids = Role.joins(:permissions).where(permissions: { name: 'ticket.agent', active: true }, roles: { active: true }).pluck(:id)
  775. count = User.joins(:roles).where(roles: { id: ticket_agent_role_ids }, users: { active: true }).distinct.count
  776. # if new added role is a ticket.agent role
  777. if ticket_agent_role_ids.include?(role.id)
  778. # if user already has a ticket.agent role
  779. hint = false
  780. role_ids.each do |locale_role_id|
  781. next if ticket_agent_role_ids.exclude?(locale_role_id)
  782. hint = true
  783. break
  784. end
  785. # user has not already a ticket.agent role
  786. if hint == false
  787. count += 1
  788. end
  789. end
  790. raise Exceptions::UnprocessableEntity, __('Agent limit exceeded, please check your account settings.') if count > Setting.get('system_agent_limit').to_i
  791. true
  792. end
  793. def domain_based_assignment
  794. return true if !email
  795. return true if organization_id
  796. begin
  797. domain = Mail::Address.new(email).domain
  798. return true if !domain
  799. organization = Organization.find_by(domain: domain.downcase, domain_assignment: true)
  800. return true if !organization
  801. self.organization_id = organization.id
  802. rescue
  803. return true
  804. end
  805. true
  806. end
  807. # sets locale of the user
  808. def set_locale
  809. # set the user's locale to the one of the "executing" user
  810. return true if !UserInfo.current_user_id
  811. user = User.find_by(id: UserInfo.current_user_id)
  812. return true if !user
  813. return true if !user.preferences[:locale]
  814. preferences[:locale] = user.preferences[:locale]
  815. true
  816. end
  817. def destroy_longer_required_objects
  818. ::Avatar.remove(self.class.to_s, id)
  819. ::UserDevice.remove(id)
  820. ::StatsStore.where(stats_storable: self).destroy_all
  821. end
  822. def destroy_move_dependency_ownership
  823. result = Models.references(self.class.to_s, id)
  824. user_columns = %w[created_by_id updated_by_id out_of_office_replacement_id origin_by_id owner_id archived_by_id published_by_id internal_by_id]
  825. result.each do |class_name, references|
  826. next if class_name.blank?
  827. next if references.blank?
  828. ref_class = class_name.constantize
  829. ref_update_columns = []
  830. references.each do |column, reference_found|
  831. next if !reference_found
  832. if user_columns.include?(column)
  833. ref_update_columns << column
  834. elsif ref_class.exists?(column => id)
  835. raise "Failed deleting references! Check logic for #{class_name}->#{column}."
  836. end
  837. end
  838. next if ref_update_columns.blank?
  839. where_sql = ref_update_columns.map { |column| "#{column} = #{id}" }.join(' OR ')
  840. ref_class.where(where_sql).find_in_batches(batch_size: 1000) do |batch_list|
  841. batch_list.each do |record|
  842. ref_update_columns.each do |column|
  843. next if record[column] != id
  844. record[column] = 1
  845. end
  846. record.save!
  847. rescue => e
  848. Rails.logger.error e
  849. end
  850. end
  851. end
  852. true
  853. end
  854. def ensure_password
  855. self.password = ensured_password
  856. end
  857. def ensured_password
  858. # ensure unset password for blank values of new users
  859. return nil if new_record? && password.blank?
  860. # don't permit empty password update for existing users
  861. return password_was if password.blank?
  862. # don't re-hash passwords
  863. return password if PasswordHash.crypted?(password)
  864. if !PasswordPolicy::MaxLength.valid? password
  865. errors.add :base, PasswordPolicy::MaxLength.error
  866. return nil
  867. end
  868. # hash the plaintext password
  869. PasswordHash.crypt(password)
  870. end
  871. # reset login_failed if password is changed
  872. def reset_login_failed_after_password_change
  873. return true if !will_save_change_to_attribute?('password')
  874. self.login_failed = 0
  875. true
  876. end
  877. # When adding/removing a phone number from the User table,
  878. # update caller ID table
  879. # to adopt/orphan matching Cti::Logs accordingly
  880. # (see https://github.com/zammad/zammad/issues/2057)
  881. def update_caller_id
  882. # skip if "phone" does not change, or changes like [nil, ""]
  883. return if persisted? && !previous_changes[:phone]&.any?(&:present?)
  884. return if destroyed? && phone.blank?
  885. Cti::CallerId.build(self)
  886. end
  887. end