user.rb 35 KB

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