user.rb 30 KB

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