token.rb 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. # Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
  2. class Token < ApplicationModel
  3. include Token::Permissions
  4. include Token::TriggersSubscriptions
  5. before_create :generate_token
  6. belongs_to :user, optional: true
  7. store :preferences
  8. scope :without_sensitive_columns, -> { select(column_names - %w[persistent token]) }
  9. =begin
  10. create new token
  11. token = Token.create(action: 'PasswordReset', user_id: user.id)
  12. returns
  13. the token
  14. create new persistent token
  15. token = Token.create(
  16. action: 'api',
  17. persistent: true,
  18. user_id: user.id,
  19. preferences: {
  20. permission: {
  21. 'user_preferences.calendar' => true,
  22. }
  23. }
  24. )
  25. in case if you use it via an controller, e. g. you can verify via "curl -H "Authorization: Token token=…" http://…
  26. returns
  27. the token
  28. =end
  29. =begin
  30. check token
  31. user = Token.check(action: 'PasswordReset', token: '123abc12qweads')
  32. check api token with permissions
  33. user = Token.check(action: 'api', token: '123abc12qweads', permission: 'admin.session')
  34. user = Token.check(action: 'api', token: '123abc12qweads', permission: ['admin.session', 'ticket.agent'])
  35. returns
  36. user for who this token was created
  37. =end
  38. def self.check(action:, token:, permission: nil, inactive_user: false)
  39. # fetch token
  40. token = Token.find_by(action:, token:)
  41. return if !token
  42. token.user if token.check?(permission:, inactive_user:)
  43. end
  44. # Check token instance validity
  45. # Invalid non-persistant instance is removed
  46. #
  47. # @param data [Hash] check options
  48. # @option data [Boolean] :inactive_user skip checking if referenced user is active
  49. # @option data [String, Array<String>] :permission check if token has given permissions
  50. #
  51. # @return [Boolean]
  52. def check?(permission: nil, inactive_user: false)
  53. if !persistent && created_at < 1.day.ago
  54. destroy
  55. return false
  56. end
  57. # persistent token not valid if user is inactive
  58. return false if !inactive_user && persistent && user.active == false
  59. # add permission check
  60. return false if permission && !permissions?(permission)
  61. true
  62. end
  63. =begin
  64. clean up all old non-persistent tokens that are older than 30 days or have their expiration date set in the past
  65. Token.cleanup
  66. =end
  67. def self.cleanup
  68. Token.where(persistent: false, created_at: ...30.days.ago).delete_all
  69. Token.where(persistent: false, expires_at: ...Time.zone.now).delete_all
  70. true
  71. end
  72. # allows to evaluate token permissions in context of given user instead of owner
  73. # @param [User] user to use as context for the given block
  74. # @param block to evaluate in given context
  75. def with_context(user:, &block)
  76. @effective_user = user
  77. instance_eval(&block) if block
  78. ensure
  79. @effective_user = nil
  80. end
  81. # fetch token for a user with a given action
  82. # checks token validity
  83. #
  84. # @param [String] action name
  85. # @param [Integer, User] user
  86. #
  87. # @return [Token, nil]
  88. def self.fetch(action, user_id = UserInfo.current_user_id)
  89. token = find_by(action: action, user_id: user_id)
  90. token if token&.check?
  91. end
  92. # Validates whether token exists, belongs to expected user and has not yet expired.
  93. # Error is raised if token is not valid
  94. #
  95. # @param action [String, Symbol] action name
  96. # @param token [String, Symbol] token value
  97. # @param user [User, Integer] expected owner of the token
  98. #
  99. # @raise [Token::InvalidToken] Raised if token is not valid
  100. # @return [Token] returns token object if all checks pass
  101. def self.validate!(action:, token:, user: UserInfo.current_user_id)
  102. token = find_by(action:, token:, user:)
  103. raise TokenAbsent if !token
  104. raise TokenExpired if token.expired?
  105. token
  106. end
  107. class TokenInvalid < StandardError; end
  108. class TokenExpired < TokenInvalid; end
  109. class TokenAbsent < TokenInvalid; end
  110. # creates or returns existing token
  111. #
  112. # @param [String] action name
  113. # @param [Integer, User] user
  114. #
  115. # @return [String]
  116. def self.ensure_token!(action, user_id = UserInfo.current_user_id, persistent: false)
  117. instance = fetch(action, user_id)
  118. return instance.token if instance.present?
  119. create!(action: action, user_id: user_id, persistent: persistent).token
  120. end
  121. # regenerates an existing token
  122. #
  123. # @param [String] action name
  124. # @param [Integer, User] user
  125. #
  126. # @return [String]
  127. def self.renew_token!(action, user_id = UserInfo.current_user_id, persistent: false)
  128. instance = fetch(action, user_id)
  129. return create(action: action, user_id: user_id, persistent: persistent).token if !instance
  130. instance.renew_token!
  131. end
  132. # regenerates an existing token
  133. #
  134. # @return [String]
  135. def renew_token!
  136. generate_token
  137. save!
  138. token
  139. end
  140. def visible_in_frontend?
  141. action == 'api' && persistent
  142. end
  143. def expired?
  144. return false if expires_at.nil?
  145. Time.zone.now >= expires_at
  146. end
  147. private
  148. def generate_token
  149. loop do
  150. self.token = SecureRandom.urlsafe_base64(48)
  151. break if !Token.exists?(token: token)
  152. end
  153. true
  154. end
  155. # token owner or user set by #with_context
  156. def effective_user
  157. @effective_user || user
  158. end
  159. end