form_controller.rb 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. # Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
  2. class FormController < ApplicationController
  3. prepend_before_action -> { authorize! }, only: %i[configuration submit]
  4. skip_before_action :verify_csrf_token
  5. before_action :cors_preflight_check
  6. after_action :set_access_control_headers_execute
  7. skip_before_action :user_device_log
  8. def configuration
  9. return if !fingerprint_exists?
  10. return if limit_reached?
  11. api_path = Rails.configuration.api_path
  12. http_type = Setting.get('http_type')
  13. fqdn = Setting.get('fqdn')
  14. endpoint = "#{http_type}://#{fqdn}#{api_path}/form_submit"
  15. result = {
  16. enabled: Setting.get('form_ticket_create'),
  17. endpoint: endpoint,
  18. token: token_gen(params[:fingerprint])
  19. }
  20. if authorized?(policy_record, :test?)
  21. result[:enabled] = true
  22. end
  23. render json: result, status: :ok
  24. end
  25. def submit
  26. return if !fingerprint_exists?
  27. return if !token_valid?(params[:token], params[:fingerprint])
  28. return if limit_reached?
  29. # validate input
  30. errors = {}
  31. if params[:name].blank?
  32. errors['name'] = 'required'
  33. end
  34. if params[:title].blank?
  35. errors['title'] = 'required'
  36. end
  37. if params[:body].blank?
  38. errors['body'] = 'required'
  39. end
  40. if params[:email].blank?
  41. errors['email'] = 'required'
  42. else
  43. begin
  44. email_address_validation = EmailAddressValidation.new(params[:email])
  45. if !email_address_validation.valid_format? || !email_address_validation.valid_mx?
  46. errors['email'] = 'invalid'
  47. end
  48. rescue => e
  49. message = e.to_s
  50. Rails.logger.info "Can't verify email #{params[:email]}: #{message}"
  51. # ignore 450, graylistings
  52. errors['email'] = message if message.exclude?('450')
  53. end
  54. end
  55. if errors.present?
  56. render json: {
  57. errors: errors
  58. }, status: :ok
  59. return
  60. end
  61. name = params[:name].strip
  62. email = params[:email].strip.downcase
  63. customer = User.find_by(email: email)
  64. if !customer
  65. role_ids = Role.signup_role_ids
  66. customer = User.create(
  67. firstname: name,
  68. lastname: '',
  69. email: email,
  70. active: true,
  71. role_ids: role_ids,
  72. updated_by_id: 1,
  73. created_by_id: 1,
  74. )
  75. end
  76. ticket = nil
  77. # set current user
  78. UserInfo.current_user_id = customer.id
  79. ApplicationHandleInfo.in_context('form') do # rubocop:disable Metrics/BlockLength
  80. group = Group.find_by(id: Setting.get('form_ticket_create_group_id'))
  81. if !group
  82. group = Group.where(active: true).first
  83. if !group
  84. group = Group.first
  85. end
  86. end
  87. ticket = Ticket.create!(
  88. group_id: group.id,
  89. customer_id: customer.id,
  90. title: params[:title],
  91. preferences: {
  92. form: {
  93. remote_ip: request.remote_ip,
  94. fingerprint_md5: Digest::MD5.hexdigest(params[:fingerprint]),
  95. }
  96. }
  97. )
  98. article = Ticket::Article.create!(
  99. ticket_id: ticket.id,
  100. type_id: Ticket::Article::Type.find_by(name: 'web').id,
  101. sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id,
  102. body: params[:body],
  103. subject: params[:title],
  104. internal: false,
  105. )
  106. params[:file]&.each do |file|
  107. Store.create!(
  108. object: 'Ticket::Article',
  109. o_id: article.id,
  110. data: file.read,
  111. filename: file.original_filename,
  112. preferences: {
  113. 'Mime-Type' => file.content_type,
  114. }
  115. )
  116. end
  117. end
  118. UserInfo.current_user_id = 1
  119. result = {
  120. ticket: {
  121. id: ticket.id,
  122. number: ticket.number
  123. }
  124. }
  125. render json: result, status: :ok
  126. end
  127. private
  128. # we don't wann to tell what the cause for the authorization error is
  129. # so we capture the exception and raise an anonymized one
  130. def authorize!(...)
  131. super
  132. rescue Pundit::NotAuthorizedError
  133. raise Exceptions::Forbidden
  134. end
  135. def token_gen(fingerprint)
  136. crypt = ActiveSupport::MessageEncryptor.new(Setting.get('application_secret')[0, 32], serializer: JSON)
  137. fingerprint = "#{Base64.strict_encode64(Setting.get('fqdn'))}:#{Time.zone.now.to_i}:#{Base64.strict_encode64(fingerprint)}"
  138. Base64.strict_encode64(crypt.encrypt_and_sign(fingerprint))
  139. end
  140. def token_valid?(token, fingerprint)
  141. if token.blank?
  142. Rails.logger.info 'No token for form!'
  143. raise Exceptions::Forbidden
  144. end
  145. begin
  146. crypt = ActiveSupport::MessageEncryptor.new(Setting.get('application_secret')[0, 32], serializer: JSON)
  147. result = crypt.decrypt_and_verify(Base64.decode64(token))
  148. rescue
  149. Rails.logger.info 'Invalid token for form!'
  150. raise Exceptions::NotAuthorized
  151. end
  152. if result.blank?
  153. Rails.logger.info 'Invalid token for form!'
  154. raise Exceptions::NotAuthorized
  155. end
  156. parts = result.split(':')
  157. if parts.count != 3
  158. Rails.logger.info "Invalid token for form (need to have 3 parts, only #{parts.count} found)!"
  159. raise Exceptions::NotAuthorized
  160. end
  161. fqdn_local = Base64.decode64(parts[0])
  162. if fqdn_local != Setting.get('fqdn')
  163. Rails.logger.info "Invalid token for form (invalid fqdn found #{fqdn_local} != #{Setting.get('fqdn')})!"
  164. raise Exceptions::NotAuthorized
  165. end
  166. fingerprint_local = Base64.decode64(parts[2])
  167. if fingerprint_local != fingerprint
  168. Rails.logger.info "Invalid token for form (invalid fingerprint found #{fingerprint_local} != #{fingerprint})!"
  169. raise Exceptions::NotAuthorized
  170. end
  171. if parts[1].to_i < (Time.zone.now.to_i - (60 * 60 * 24))
  172. Rails.logger.info 'Invalid token for form (token expired})!'
  173. raise Exceptions::NotAuthorized
  174. end
  175. true
  176. end
  177. def limit_reached?
  178. return false if !SearchIndexBackend.enabled?
  179. # quote ipv6 ip'
  180. remote_ip = request.remote_ip.gsub(':', '\\:')
  181. # in elasticsearch7 "created_at:>now-1h" is not working. Needed to catch -2h
  182. form_limit_by_ip_per_hour = Setting.get('form_ticket_create_by_ip_per_hour') || 20
  183. result = SearchIndexBackend.search("preferences.form.remote_ip:'#{remote_ip}' AND created_at:>now-2h", 'Ticket', limit: form_limit_by_ip_per_hour)
  184. raise Exceptions::Forbidden if result.count >= form_limit_by_ip_per_hour.to_i
  185. form_limit_by_ip_per_day = Setting.get('form_ticket_create_by_ip_per_day') || 240
  186. result = SearchIndexBackend.search("preferences.form.remote_ip:'#{remote_ip}' AND created_at:>now-1d", 'Ticket', limit: form_limit_by_ip_per_day)
  187. raise Exceptions::Forbidden if result.count >= form_limit_by_ip_per_day.to_i
  188. form_limit_per_day = Setting.get('form_ticket_create_per_day') || 5000
  189. result = SearchIndexBackend.search('preferences.form.remote_ip:* AND created_at:>now-1d', 'Ticket', limit: form_limit_per_day)
  190. raise Exceptions::Forbidden if result.count >= form_limit_per_day.to_i
  191. false
  192. end
  193. def fingerprint_exists?
  194. return true if params[:fingerprint].present? && params[:fingerprint].length > 30
  195. Rails.logger.info "The required parameter 'fingerprint' is missing or invalid."
  196. raise Exceptions::Forbidden
  197. end
  198. end