user.rb 33 KB

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