5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-11-22 18:16:22 +00:00
panel/app/controllers/api/v1/protected_controller.rb
2020-10-03 21:46:24 -03:00

140 lines
4.6 KiB
Ruby

# frozen_string_literal: true
module Api
module V1
# API para recibir información sin autenticación
class ProtectedController < BaseController
# Permitir conexiones desde varios sitios, estamos chequeando más
# adelante.
skip_forgery_protection
# Aplicar algunos chequeos básicos. Deberíamos registrar de
# alguna forma los errores pero tampoco queremos que nos usen
# recursos.
#
# Devolvemos un error 428: Precondition Required dando la
# oportunidad a les visitantes de reintentar en el caso de falsos
# positivos. No devolvemos contenido para que el servidor web
# capture y muestra la página de error específica de cada sitio o
# la genérica de Sutty.
#
# XXX: Ordenar en orden ascendiente según uso de recursos.
before_action :cookie_is_valid?, unless: :performed?
before_action :valid_authenticity_token_in_cookie?, unless: :performed?
before_action :site_exists?, unless: :performed?
before_action :site_is_origin?, unless: :performed?
before_action :destination_exists?, unless: :performed?
before_action :from_is_address?, unless: :performed?
before_action :gave_consent?, unless: :performed?
# Reescribir performed? para registrar lo que pasó en el camino a
# menos que esté todo bien.
def performed?
if (performed = super) && response.status >= 400
log_entry if ENV['DEBUG_FORMS'].present?
end
performed
end
private
def site_cookie
@site_cookie ||= cookies.encrypted[site_id]
end
# Comprueba que no se haya reutilizado una cookie vencida
#
# XXX: Si el navegador envió una cookie vencida es porque la está
# reutilizando, probablemente de forma maliciosa? Pero también
# puede ser que haya tardado más de media hora en enviar el
# formulario.
def cookie_is_valid?
return if (site_cookie&.dig('expires') || 0) > Time.now.to_i
@reason = 'expired_or_invalid_cookie'
render plain: Rails.env.production? ? nil : @reason, status: :precondition_required
end
# Queremos comprobar que la cookie corresponda con la sesión. La
# cookie puede haber vencido, así que es uno de los chequeos más
# simples que hacemos.
#
# TODO: Pensar una forma de redirigir al origen sin vaciar el
# formulario para que le usuarie recargue la cookie.
def valid_authenticity_token_in_cookie?
return if valid_authenticity_token? session, site_cookie['csrf']
@reason = 'invalid_auth_token'
render plain: Rails.env.production? ? nil : @reason, status: :precondition_required
end
# Comprueba que el sitio existe
#
# TODO: Responder con una zip bomb!
def site_exists?
return unless site.nil?
@reason = 'site_does_not_exist'
render plain: Rails.env.production? ? nil : @reason, status: :precondition_required
end
# Comprueba que el mensaje fue enviado desde el sitio o uno
# de los sitios permitidos.
#
# XXX: Este header se puede falsificar de todas formas pero al
# menos es una trampa.
def site_is_origin?
return if origin? && site.urls(slash: false).any? { |u| origin.to_s.start_with? u }
@reason = 'site_is_not_origin'
render plain: Rails.env.production? ? nil : @reason, status: :precondition_required
end
# Detecta si la dirección de contacto es válida. Además es
# opcional.
def from_is_address?
raise NotImplementedError
end
# No aceptar nada si no dió su consentimiento
def gave_consent?
raise NotImplementedError
end
def post?
params[:layout].present?
end
def form?
params[:form].present?
end
# Los campos que se envían tienen que corresponder con un
# formulario de contacto o un layout
def destination_exists?
raise NotImplementedError
end
# Encuentra el sitio o devuelve nulo
def site
@site ||= Site.find_by(name: site_id)
end
# Genera un registro con información básica para debug, quizás no
# quede asociado a ningún sitio.
#
# TODO: Chequear qué pasa con los archivos adjuntos.
#
# @return [TrueClass]
def log_entry
LogEntry.create site: site || Site.default, text: {
reason: @reason,
status: response.status,
headers: request.headers.to_h.select { |k, _| /\A[A-Z]/ =~ k },
params: params
}
end
end
end
end