handles_errors.rb 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. module ApplicationController::HandlesErrors
  3. extend ActiveSupport::Concern
  4. included do
  5. rescue_from StandardError, with: :internal_server_error
  6. rescue_from 'ExecJS::RuntimeError', with: :internal_server_error
  7. rescue_from ActiveRecord::RecordNotFound, with: :not_found
  8. rescue_from ActiveRecord::StatementInvalid, with: :unprocessable_entity
  9. rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity
  10. rescue_from ActiveRecord::DeleteRestrictionError, with: :unprocessable_entity
  11. rescue_from ArgumentError, with: :unprocessable_entity
  12. rescue_from Exceptions::UnprocessableEntity, with: :unprocessable_entity
  13. rescue_from Exceptions::NotAuthorized, with: :unauthorized
  14. rescue_from Exceptions::Forbidden, with: :forbidden
  15. rescue_from Pundit::NotAuthorizedError, with: :pundit_not_authorized_error
  16. end
  17. def not_found(e)
  18. logger.error e
  19. respond_to_exception(e, :not_found)
  20. http_log
  21. end
  22. def unprocessable_entity(e)
  23. logger.error e
  24. respond_to_exception(e, :unprocessable_entity)
  25. http_log
  26. end
  27. def internal_server_error(e)
  28. logger.error e
  29. respond_to_exception(e, :internal_server_error)
  30. http_log
  31. end
  32. def unauthorized(e)
  33. logger.info { e }
  34. error = humanize_error(e)
  35. response.headers['X-Failure'] = error.fetch(:error_human, error[:error])
  36. respond_to_exception(e, :unauthorized)
  37. http_log
  38. end
  39. def forbidden(e)
  40. logger.info { e }
  41. error = humanize_error(e)
  42. response.headers['X-Failure'] = error.fetch(:error_human, error[:error])
  43. respond_to_exception(e, :forbidden)
  44. http_log
  45. end
  46. def pundit_not_authorized_error(e)
  47. logger.info { e }
  48. # check if a special authorization_error should be shown in the result payload
  49. # which was raised in one of the policies. Fall back to a simple "Not authorized"
  50. # error to hide actual cause for security reasons.
  51. exception = e.policy&.custom_exception || Exceptions::Forbidden.new(__('Not authorized'))
  52. case exception
  53. when ActiveRecord::RecordNotFound
  54. not_found(exception)
  55. when Exceptions::UnprocessableEntity
  56. unprocessable_entity(exception)
  57. else
  58. forbidden(exception)
  59. end
  60. end
  61. private
  62. def respond_to_exception(e, status)
  63. status_code = Rack::Utils.status_code(status)
  64. respond_to do |format|
  65. format.json { render json: humanize_error(e), status: status }
  66. format.any do
  67. errors = humanize_error(e)
  68. @exception = e
  69. @message = errors[:error_human] || errors[:error] || param[:message]
  70. @traceback = !Rails.env.production?
  71. file = Rails.public_path.join("#{status_code}.html").open('r')
  72. render inline: file.read, status: status, content_type: 'text/html' # rubocop:disable Rails/RenderInline
  73. end
  74. end
  75. end
  76. def humanize_error(e)
  77. data = {
  78. error: e.message
  79. }
  80. if (base_error = e.try(:record)&.errors&.messages&.find { |key, _| key.match? %r{[\w+.]?base} }&.last&.last)
  81. data[:error_human] = base_error
  82. elsif (first_error = e.try(:record)&.errors&.full_messages&.first)
  83. data[:error_human] = first_error
  84. elsif e.message.match?(%r{(already exists|duplicate key|duplicate entry)}i)
  85. data[:error_human] = __('This object already exists.')
  86. elsif e.message =~ %r{null value in column "(.+?)" violates not-null constraint}i || e.message =~ %r{Field '(.+?)' doesn't have a default value}i
  87. data[:error_human] = "Attribute '#{$1}' required!"
  88. elsif e.message == 'Exceptions::Forbidden'
  89. data[:error] = __('Not authorized')
  90. data[:error_human] = data[:error]
  91. elsif e.message == 'Exceptions::NotAuthorized'
  92. data[:error] = __('Authorization failed')
  93. data[:error_human] = data[:error]
  94. elsif [ActionController::RoutingError, ActiveRecord::RecordNotFound, Exceptions::UnprocessableEntity, Exceptions::NotAuthorized, Exceptions::Forbidden].include?(e.class)
  95. data[:error_human] = data[:error]
  96. end
  97. if data[:error_human].present?
  98. data[:error] = data[:error_human]
  99. elsif !policy(Exceptions).view_details?
  100. error_code_prefix = "Error ID #{SecureRandom.urlsafe_base64(6)}:"
  101. Rails.logger.error "#{error_code_prefix} #{data[:error]}"
  102. data[:error] = "#{error_code_prefix} Please contact your administrator."
  103. end
  104. data
  105. end
  106. end