user.rb 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085
  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. private
  511. def organization_history_log(org, type)
  512. return if id.blank?
  513. attributes = {
  514. history_attribute: 'organization_ids',
  515. id_to: org.id,
  516. value_to: org.name
  517. }
  518. history_log(type, id, attributes)
  519. end
  520. def check_name
  521. self.firstname = sanitize_name(firstname)
  522. self.lastname = sanitize_name(lastname)
  523. return if firstname.present? && lastname.present?
  524. if (firstname.blank? && lastname.present?) || (firstname.present? && lastname.blank?)
  525. used_name = firstname.presence || lastname
  526. (local_firstname, local_lastname) = User.name_guess(used_name, email)
  527. elsif firstname.blank? && lastname.blank? && email.present?
  528. (local_firstname, local_lastname) = User.name_guess('', email)
  529. end
  530. check_name_apply(:firstname, local_firstname)
  531. check_name_apply(:lastname, local_lastname)
  532. end
  533. def sanitize_name(value)
  534. result = value&.strip
  535. return result if result.blank?
  536. result.split(%r{\s}).map { |v| strip_uri(v) }.join("\s")
  537. end
  538. def strip_uri(value)
  539. uri = URI.parse(value)
  540. return value if !uri || uri.scheme.blank? || uri.hostname.blank?
  541. # Strip the scheme from the URI.
  542. uri.hostname + uri.path
  543. rescue
  544. value
  545. end
  546. def check_name_apply(identifier, input)
  547. self[identifier] = input if input.present?
  548. self[identifier].capitalize! if self[identifier]&.match? %r{^([[:upper:]]+|[[:lower:]]+)$}
  549. end
  550. def check_email
  551. return if Setting.get('import_mode')
  552. return if email.blank?
  553. # https://bugs.chromium.org/p/chromium/issues/detail?id=410937
  554. self.email = EmailHelper::Idn.to_unicode(email).downcase.strip
  555. end
  556. def ensure_email
  557. return if Setting.get('import_mode')
  558. return if email.blank?
  559. return if id == 1
  560. email_address_validation = EmailAddressValidation.new(email)
  561. return if email_address_validation.valid?
  562. errors.add :base, __("Invalid email '%{email}'"), email: email
  563. end
  564. def check_login
  565. # use email as login if not given
  566. if login.blank?
  567. self.login = email
  568. end
  569. # if email has changed, login is old email, change also login
  570. if email_changed? && email_was == login
  571. self.login = email
  572. end
  573. # generate auto login
  574. if login.blank?
  575. self.login = "auto-#{SecureRandom.uuid}"
  576. end
  577. # check if login already exists
  578. base_login = login.downcase.strip
  579. alternatives = [nil] + Array(1..20) + [ SecureRandom.uuid ]
  580. alternatives.each do |suffix|
  581. self.login = "#{base_login}#{suffix}"
  582. exists = User.find_by(login: login)
  583. return true if !exists || exists.id == id
  584. end
  585. raise Exceptions::UnprocessableEntity, "Invalid user login generation for login #{login}!"
  586. end
  587. def check_mail_delivery_failed
  588. return if email_change.blank?
  589. preferences.delete(:mail_delivery_failed)
  590. end
  591. def ensure_roles
  592. return if role_ids.present?
  593. self.role_ids = Role.signup_role_ids
  594. end
  595. def ensure_identifier
  596. return if login.present? && !login.start_with?('auto-')
  597. return if [email, firstname, lastname, phone, mobile].any?(&:present?)
  598. errors.add :base, __('At least one identifier (firstname, lastname, phone, mobile or email) for user is required.')
  599. end
  600. def ensure_uniq_email
  601. return if Setting.get('user_email_multiple_use')
  602. return if Setting.get('import_mode')
  603. return if email.blank?
  604. return if !email_changed?
  605. return if !User.exists?(email: email.downcase.strip)
  606. errors.add :base, __("Email address '%{email}' is already used for another user."), email: email.downcase.strip
  607. end
  608. def ensure_organizations
  609. return if organization_ids.blank?
  610. return if organization_id.present?
  611. errors.add :base, __('Secondary organizations are only allowed when the primary organization is given.')
  612. end
  613. def ensure_organizations_limit
  614. return if organization_ids.size <= 250
  615. errors.add :base, __('More than 250 secondary organizations are not allowed.')
  616. end
  617. def validate_roles(role)
  618. return true if !role_ids # we need role_ids for checking in role_ids below, in this method
  619. return true if role.preferences[:not].blank?
  620. role.preferences[:not].each do |local_role_name|
  621. local_role = Role.lookup(name: local_role_name)
  622. next if !local_role
  623. next if role_ids.exclude?(local_role.id)
  624. raise "Role #{role.name} conflicts with #{local_role.name}"
  625. end
  626. true
  627. end
  628. def validate_preferences
  629. return true if !changes
  630. return true if !changes['preferences']
  631. return true if preferences.blank?
  632. return true if !preferences[:notification_sound]
  633. return true if !preferences[:notification_sound][:enabled]
  634. case preferences[:notification_sound][:enabled]
  635. when 'true'
  636. preferences[:notification_sound][:enabled] = true
  637. when 'false'
  638. preferences[:notification_sound][:enabled] = false
  639. end
  640. class_name = preferences[:notification_sound][:enabled].class.to_s
  641. 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'
  642. true
  643. end
  644. def ensure_notification_preferences
  645. fill_notification_config_preferences
  646. self.reset_notification_config_before_save = false
  647. end
  648. =begin
  649. checks if the current user is the last one with admin permissions.
  650. Raises
  651. raise 'At least one user need to have admin permissions'
  652. =end
  653. def last_admin_check_by_attribute
  654. return true if !will_save_change_to_attribute?('active')
  655. return true if active != false
  656. return true if !permissions?(['admin', 'admin.user'])
  657. raise Exceptions::UnprocessableEntity, __('At least one user needs to have admin permissions.') if last_admin_check_admin_count < 1
  658. true
  659. end
  660. def last_admin_check_by_role(role)
  661. return true if Setting.get('import_mode')
  662. return true if !role.with_permission?(['admin', 'admin.user'])
  663. raise Exceptions::UnprocessableEntity, __('At least one user needs to have admin permissions.') if last_admin_check_admin_count < 1
  664. true
  665. end
  666. def last_admin_check_admin_count
  667. admin_role_ids = Role.joins(:permissions).where(permissions: { name: ['admin', 'admin.user'], active: true }, roles: { active: true }).pluck(:id)
  668. User.joins(:roles).where(roles: { id: admin_role_ids }, users: { active: true }).distinct.count - 1
  669. end
  670. def validate_agent_limit_by_attributes
  671. return true if Setting.get('system_agent_limit').blank?
  672. return true if !will_save_change_to_attribute?('active')
  673. return true if active != true
  674. return true if !permissions?('ticket.agent')
  675. ticket_agent_role_ids = Role.joins(:permissions).where(permissions: { name: 'ticket.agent', active: true }, roles: { active: true }).pluck(:id)
  676. count = User.joins(:roles).where(roles: { id: ticket_agent_role_ids }, users: { active: true }).distinct.count + 1
  677. raise Exceptions::UnprocessableEntity, __('Agent limit exceeded, please check your account settings.') if count > Setting.get('system_agent_limit').to_i
  678. true
  679. end
  680. def validate_agent_limit_by_role(role)
  681. return true if Setting.get('system_agent_limit').blank?
  682. return true if active != true
  683. return true if role.active != true
  684. return true if !role.with_permission?('ticket.agent')
  685. ticket_agent_role_ids = Role.joins(:permissions).where(permissions: { name: 'ticket.agent', active: true }, roles: { active: true }).pluck(:id)
  686. count = User.joins(:roles).where(roles: { id: ticket_agent_role_ids }, users: { active: true }).distinct.count
  687. # if new added role is a ticket.agent role
  688. if ticket_agent_role_ids.include?(role.id)
  689. # if user already has a ticket.agent role
  690. hint = false
  691. role_ids.each do |locale_role_id|
  692. next if ticket_agent_role_ids.exclude?(locale_role_id)
  693. hint = true
  694. break
  695. end
  696. # user has not already a ticket.agent role
  697. if hint == false
  698. count += 1
  699. end
  700. end
  701. raise Exceptions::UnprocessableEntity, __('Agent limit exceeded, please check your account settings.') if count > Setting.get('system_agent_limit').to_i
  702. true
  703. end
  704. def domain_based_assignment
  705. return true if !email
  706. return true if organization_id
  707. begin
  708. domain = Mail::Address.new(email).domain
  709. return true if !domain
  710. organization = Organization.find_by(domain: domain.downcase, domain_assignment: true)
  711. return true if !organization
  712. self.organization_id = organization.id
  713. rescue
  714. return true
  715. end
  716. true
  717. end
  718. # sets locale of the user
  719. def set_locale
  720. # set the user's locale to the one of the "executing" user
  721. return true if !UserInfo.current_user_id
  722. user = UserInfo.current_user
  723. return true if !user
  724. return true if !user.preferences[:locale]
  725. preferences[:locale] = user.preferences[:locale]
  726. true
  727. end
  728. def destroy_longer_required_objects
  729. ::Avatar.remove(self.class.to_s, id)
  730. ::UserDevice.remove(id)
  731. ::StatsStore.where(stats_storable: self).destroy_all
  732. end
  733. def destroy_move_dependency_ownership
  734. result = Models.references(self.class.to_s, id)
  735. 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]
  736. result.each do |class_name, references|
  737. next if class_name.blank?
  738. next if references.blank?
  739. ref_class = class_name.constantize
  740. ref_update_columns = []
  741. references.each do |column, reference_found|
  742. next if !reference_found
  743. if user_columns.include?(column)
  744. ref_update_columns << column
  745. elsif ref_class.exists?(column => id)
  746. raise "Failed deleting references! Check logic for #{class_name}->#{column}."
  747. end
  748. end
  749. next if ref_update_columns.blank?
  750. where_sql = ref_update_columns.map { |column| "#{column} = #{id}" }.join(' OR ')
  751. ref_class.where(where_sql).find_in_batches(batch_size: 1000) do |batch_list|
  752. batch_list.each do |record|
  753. ref_update_columns.each do |column|
  754. next if record[column] != id
  755. record[column] = 1
  756. end
  757. record.save!(validate: false)
  758. rescue => e
  759. Rails.logger.error e
  760. end
  761. end
  762. end
  763. true
  764. end
  765. def ensure_password
  766. return if !password_changed?
  767. self.password = ensured_password
  768. end
  769. def ensured_password
  770. # ensure unset password for blank values of new users
  771. return nil if new_record? && password.blank?
  772. # don't permit empty password update for existing users
  773. return password_was if password.blank?
  774. # don't re-hash passwords
  775. return password if PasswordHash.crypted?(password)
  776. if !PasswordPolicy::MaxLength.valid? password
  777. errors.add :password, __('is too long')
  778. return nil
  779. end
  780. # hash the plaintext password
  781. PasswordHash.crypt(password)
  782. end
  783. # reset login_failed if password is changed
  784. def reset_login_failed_after_password_change
  785. return true if !will_save_change_to_attribute?('password')
  786. self.login_failed = 0
  787. true
  788. end
  789. # When adding/removing a phone/mobile number from the User table,
  790. # update caller ID table
  791. # to adopt/orphan matching Cti::Logs accordingly
  792. # (see https://github.com/zammad/zammad/issues/2057)
  793. def update_caller_id
  794. # skip if "phone/mobile" does not change, or changes like [nil, ""]
  795. return if persisted? && previous_changes.slice(:phone, :mobile).values.flatten.none?(&:present?)
  796. return if destroyed? && phone.blank? && mobile.blank?
  797. Cti::CallerId.build(self)
  798. end
  799. end