user.rb 32 KB

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