2022-01-01 13:38:12 +00:00
|
|
|
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
|
2015-08-10 00:10:41 +00:00
|
|
|
|
|
|
|
class FormController < ApplicationController
|
2020-03-19 09:39:51 +00:00
|
|
|
prepend_before_action -> { authorize! }, only: %i[configuration submit]
|
|
|
|
|
2017-02-15 12:29:25 +00:00
|
|
|
skip_before_action :verify_csrf_token
|
2020-02-13 10:49:33 +00:00
|
|
|
before_action :cors_preflight_check
|
2017-02-15 12:29:25 +00:00
|
|
|
after_action :set_access_control_headers_execute
|
2021-07-23 13:07:16 +00:00
|
|
|
skip_before_action :user_device_log
|
2015-08-10 00:10:41 +00:00
|
|
|
|
2017-09-05 14:54:22 +00:00
|
|
|
def configuration
|
2017-06-28 17:13:52 +00:00
|
|
|
return if !fingerprint_exists?
|
|
|
|
return if limit_reached?
|
2015-08-10 00:10:41 +00:00
|
|
|
|
|
|
|
api_path = Rails.configuration.api_path
|
|
|
|
http_type = Setting.get('http_type')
|
|
|
|
fqdn = Setting.get('fqdn')
|
|
|
|
|
|
|
|
endpoint = "#{http_type}://#{fqdn}#{api_path}/form_submit"
|
|
|
|
|
2017-09-05 14:54:22 +00:00
|
|
|
result = {
|
2015-08-10 00:10:41 +00:00
|
|
|
enabled: Setting.get('form_ticket_create'),
|
|
|
|
endpoint: endpoint,
|
2017-06-28 17:13:52 +00:00
|
|
|
token: token_gen(params[:fingerprint])
|
2015-08-10 00:10:41 +00:00
|
|
|
}
|
|
|
|
|
2020-03-19 09:39:51 +00:00
|
|
|
if authorized?(policy_record, :test?)
|
2017-09-05 14:54:22 +00:00
|
|
|
result[:enabled] = true
|
2016-10-06 19:01:48 +00:00
|
|
|
end
|
|
|
|
|
2017-09-05 14:54:22 +00:00
|
|
|
render json: result, status: :ok
|
2015-08-10 00:10:41 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def submit
|
2017-06-28 17:13:52 +00:00
|
|
|
return if !fingerprint_exists?
|
|
|
|
return if !token_valid?(params[:token], params[:fingerprint])
|
|
|
|
return if limit_reached?
|
2015-08-10 00:10:41 +00:00
|
|
|
|
|
|
|
# validate input
|
|
|
|
errors = {}
|
2017-06-28 17:13:52 +00:00
|
|
|
if params[:name].blank?
|
2015-08-10 00:10:41 +00:00
|
|
|
errors['name'] = 'required'
|
|
|
|
end
|
2017-06-28 17:13:52 +00:00
|
|
|
if params[:title].blank?
|
2015-08-10 08:56:55 +00:00
|
|
|
errors['title'] = 'required'
|
|
|
|
end
|
2017-06-28 17:13:52 +00:00
|
|
|
if params[:body].blank?
|
2015-08-10 00:10:41 +00:00
|
|
|
errors['body'] = 'required'
|
|
|
|
end
|
|
|
|
|
2019-11-21 15:49:14 +00:00
|
|
|
if params[:email].blank?
|
|
|
|
errors['email'] = 'required'
|
|
|
|
else
|
2016-02-01 09:23:55 +00:00
|
|
|
begin
|
2019-11-21 15:49:14 +00:00
|
|
|
email_address_validation = EmailAddressValidation.new(params[:email])
|
|
|
|
if !email_address_validation.valid_format? || !email_address_validation.valid_mx?
|
2017-06-28 17:13:52 +00:00
|
|
|
errors['email'] = 'invalid'
|
2016-02-01 09:23:55 +00:00
|
|
|
end
|
|
|
|
rescue => e
|
2016-02-01 10:28:44 +00:00
|
|
|
message = e.to_s
|
|
|
|
Rails.logger.info "Can't verify email #{params[:email]}: #{message}"
|
|
|
|
|
|
|
|
# ignore 450, graylistings
|
2020-09-30 09:07:01 +00:00
|
|
|
errors['email'] = message if message.exclude?('450')
|
2016-02-01 09:23:55 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-28 17:13:52 +00:00
|
|
|
if errors.present?
|
2015-08-10 00:10:41 +00:00
|
|
|
render json: {
|
|
|
|
errors: errors
|
|
|
|
}, status: :ok
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
name = params[:name].strip
|
|
|
|
email = params[:email].strip.downcase
|
|
|
|
|
|
|
|
customer = User.find_by(email: email)
|
|
|
|
if !customer
|
2016-08-12 16:39:09 +00:00
|
|
|
role_ids = Role.signup_role_ids
|
2015-08-10 00:10:41 +00:00
|
|
|
customer = User.create(
|
2018-12-19 17:31:51 +00:00
|
|
|
firstname: name,
|
|
|
|
lastname: '',
|
|
|
|
email: email,
|
|
|
|
active: true,
|
|
|
|
role_ids: role_ids,
|
2015-08-10 00:10:41 +00:00
|
|
|
updated_by_id: 1,
|
|
|
|
created_by_id: 1,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2021-08-20 03:36:30 +00:00
|
|
|
ticket = nil
|
|
|
|
|
2016-10-06 16:56:10 +00:00
|
|
|
# set current user
|
|
|
|
UserInfo.current_user_id = customer.id
|
2021-08-20 03:36:30 +00:00
|
|
|
ApplicationHandleInfo.in_context('form') do # rubocop:disable Metrics/BlockLength
|
|
|
|
group = Group.find_by(id: Setting.get('form_ticket_create_group_id'))
|
2017-06-28 17:19:19 +00:00
|
|
|
if !group
|
2021-08-20 03:36:30 +00:00
|
|
|
group = Group.where(active: true).first
|
|
|
|
if !group
|
|
|
|
group = Group.first
|
|
|
|
end
|
2017-06-28 17:19:19 +00:00
|
|
|
end
|
2021-08-20 03:36:30 +00:00
|
|
|
ticket = Ticket.create!(
|
|
|
|
group_id: group.id,
|
|
|
|
customer_id: customer.id,
|
|
|
|
title: params[:title],
|
2017-11-23 08:09:44 +00:00
|
|
|
preferences: {
|
2021-08-20 03:36:30 +00:00
|
|
|
form: {
|
|
|
|
remote_ip: request.remote_ip,
|
|
|
|
fingerprint_md5: Digest::MD5.hexdigest(params[:fingerprint]),
|
|
|
|
}
|
2017-11-23 08:09:44 +00:00
|
|
|
}
|
|
|
|
)
|
2021-08-20 03:36:30 +00:00
|
|
|
article = Ticket::Article.create!(
|
|
|
|
ticket_id: ticket.id,
|
|
|
|
type_id: Ticket::Article::Type.find_by(name: 'web').id,
|
|
|
|
sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id,
|
|
|
|
body: params[:body],
|
|
|
|
subject: params[:title],
|
|
|
|
internal: false,
|
|
|
|
)
|
|
|
|
|
|
|
|
params[:file]&.each do |file|
|
|
|
|
Store.add(
|
|
|
|
object: 'Ticket::Article',
|
|
|
|
o_id: article.id,
|
|
|
|
data: file.read,
|
|
|
|
filename: file.original_filename,
|
|
|
|
preferences: {
|
|
|
|
'Mime-Type' => file.content_type,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
end
|
2017-01-09 14:16:29 +00:00
|
|
|
end
|
|
|
|
|
2016-10-06 16:56:10 +00:00
|
|
|
UserInfo.current_user_id = 1
|
|
|
|
|
2017-01-09 12:45:22 +00:00
|
|
|
result = {
|
|
|
|
ticket: {
|
2018-12-19 17:31:51 +00:00
|
|
|
id: ticket.id,
|
2017-01-09 12:45:22 +00:00
|
|
|
number: ticket.number
|
|
|
|
}
|
|
|
|
}
|
2015-08-10 00:10:41 +00:00
|
|
|
render json: result, status: :ok
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2020-03-19 09:39:51 +00:00
|
|
|
# we don't wann to tell what the cause for the authorization error is
|
|
|
|
# so we capture the exception and raise an anonymized one
|
2021-07-06 07:52:22 +00:00
|
|
|
def authorize!(...)
|
2020-03-19 09:39:51 +00:00
|
|
|
super
|
|
|
|
rescue Pundit::NotAuthorizedError
|
2021-02-04 08:28:41 +00:00
|
|
|
raise Exceptions::Forbidden
|
2020-03-19 09:39:51 +00:00
|
|
|
end
|
|
|
|
|
2017-06-28 17:13:52 +00:00
|
|
|
def token_gen(fingerprint)
|
2021-09-01 07:46:00 +00:00
|
|
|
crypt = ActiveSupport::MessageEncryptor.new(Setting.get('application_secret')[0, 32], serializer: JSON)
|
2017-06-28 17:13:52 +00:00
|
|
|
fingerprint = "#{Base64.strict_encode64(Setting.get('fqdn'))}:#{Time.zone.now.to_i}:#{Base64.strict_encode64(fingerprint)}"
|
|
|
|
Base64.strict_encode64(crypt.encrypt_and_sign(fingerprint))
|
|
|
|
end
|
|
|
|
|
|
|
|
def token_valid?(token, fingerprint)
|
|
|
|
if token.blank?
|
|
|
|
Rails.logger.info 'No token for form!'
|
2021-02-04 08:28:41 +00:00
|
|
|
raise Exceptions::Forbidden
|
2017-06-28 17:13:52 +00:00
|
|
|
end
|
|
|
|
begin
|
2021-09-01 07:46:00 +00:00
|
|
|
crypt = ActiveSupport::MessageEncryptor.new(Setting.get('application_secret')[0, 32], serializer: JSON)
|
2017-06-28 17:13:52 +00:00
|
|
|
result = crypt.decrypt_and_verify(Base64.decode64(token))
|
|
|
|
rescue
|
|
|
|
Rails.logger.info 'Invalid token for form!'
|
2019-02-26 10:37:31 +00:00
|
|
|
raise Exceptions::NotAuthorized
|
2017-06-28 17:13:52 +00:00
|
|
|
end
|
|
|
|
if result.blank?
|
|
|
|
Rails.logger.info 'Invalid token for form!'
|
2019-02-26 10:37:31 +00:00
|
|
|
raise Exceptions::NotAuthorized
|
2017-06-28 17:13:52 +00:00
|
|
|
end
|
2021-03-02 07:26:51 +00:00
|
|
|
parts = result.split(':')
|
2017-06-28 17:13:52 +00:00
|
|
|
if parts.count != 3
|
|
|
|
Rails.logger.info "Invalid token for form (need to have 3 parts, only #{parts.count} found)!"
|
2019-02-26 10:37:31 +00:00
|
|
|
raise Exceptions::NotAuthorized
|
2017-06-28 17:13:52 +00:00
|
|
|
end
|
|
|
|
fqdn_local = Base64.decode64(parts[0])
|
|
|
|
if fqdn_local != Setting.get('fqdn')
|
|
|
|
Rails.logger.info "Invalid token for form (invalid fqdn found #{fqdn_local} != #{Setting.get('fqdn')})!"
|
2019-02-26 10:37:31 +00:00
|
|
|
raise Exceptions::NotAuthorized
|
2017-06-28 17:13:52 +00:00
|
|
|
end
|
|
|
|
fingerprint_local = Base64.decode64(parts[2])
|
|
|
|
if fingerprint_local != fingerprint
|
|
|
|
Rails.logger.info "Invalid token for form (invalid fingerprint found #{fingerprint_local} != #{fingerprint})!"
|
2019-02-26 10:37:31 +00:00
|
|
|
raise Exceptions::NotAuthorized
|
2017-06-28 17:13:52 +00:00
|
|
|
end
|
2021-09-14 07:46:08 +00:00
|
|
|
if parts[1].to_i < (Time.zone.now.to_i - (60 * 60 * 24))
|
2017-06-28 17:13:52 +00:00
|
|
|
Rails.logger.info 'Invalid token for form (token expired})!'
|
2019-02-26 10:37:31 +00:00
|
|
|
raise Exceptions::NotAuthorized
|
2017-06-28 17:13:52 +00:00
|
|
|
end
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def limit_reached?
|
|
|
|
return false if !SearchIndexBackend.enabled?
|
|
|
|
|
2019-06-20 10:45:27 +00:00
|
|
|
# quote ipv6 ip'
|
|
|
|
remote_ip = request.remote_ip.gsub(':', '\\:')
|
|
|
|
|
|
|
|
# in elasticsearch7 "created_at:>now-1h" is not working. Needed to catch -2h
|
2017-06-28 17:13:52 +00:00
|
|
|
form_limit_by_ip_per_hour = Setting.get('form_ticket_create_by_ip_per_hour') || 20
|
2019-06-20 10:45:27 +00:00
|
|
|
result = SearchIndexBackend.search("preferences.form.remote_ip:'#{remote_ip}' AND created_at:>now-2h", 'Ticket', limit: form_limit_by_ip_per_hour)
|
2021-02-04 08:28:41 +00:00
|
|
|
raise Exceptions::Forbidden if result.count >= form_limit_by_ip_per_hour.to_i
|
2017-06-28 17:13:52 +00:00
|
|
|
|
|
|
|
form_limit_by_ip_per_day = Setting.get('form_ticket_create_by_ip_per_day') || 240
|
2019-06-20 10:45:27 +00:00
|
|
|
result = SearchIndexBackend.search("preferences.form.remote_ip:'#{remote_ip}' AND created_at:>now-1d", 'Ticket', limit: form_limit_by_ip_per_day)
|
2021-02-04 08:28:41 +00:00
|
|
|
raise Exceptions::Forbidden if result.count >= form_limit_by_ip_per_day.to_i
|
2017-06-28 17:13:52 +00:00
|
|
|
|
|
|
|
form_limit_per_day = Setting.get('form_ticket_create_per_day') || 5000
|
2018-11-06 16:11:10 +00:00
|
|
|
result = SearchIndexBackend.search('preferences.form.remote_ip:* AND created_at:>now-1d', 'Ticket', limit: form_limit_per_day)
|
2021-02-04 08:28:41 +00:00
|
|
|
raise Exceptions::Forbidden if result.count >= form_limit_per_day.to_i
|
2017-06-28 17:13:52 +00:00
|
|
|
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
def fingerprint_exists?
|
|
|
|
return true if params[:fingerprint].present? && params[:fingerprint].length > 30
|
2018-10-09 06:17:41 +00:00
|
|
|
|
2022-02-16 07:41:51 +00:00
|
|
|
Rails.logger.info "The required parameter 'fingerprint' is missing or invalid."
|
2021-02-04 08:28:41 +00:00
|
|
|
raise Exceptions::Forbidden
|
2017-06-28 17:13:52 +00:00
|
|
|
end
|
2015-08-10 00:10:41 +00:00
|
|
|
end
|