user.rb 29 KB

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