user.rb 29 KB

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