mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-15 04:41:43 +00:00
141 lines
4.4 KiB
Ruby
141 lines
4.4 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.try(:[], 'expires') || 0) > Time.now.to_i
|
||
|
|
||
|
@reason = 'expired_or_invalid_cookie'
|
||
|
head :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'
|
||
|
head :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'
|
||
|
head :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 site.urls(slash: false).any? { |u| origin.to_s.start_with? u }
|
||
|
|
||
|
@reason = 'site_is_not_origin'
|
||
|
head :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 || default_site, text: {
|
||
|
reason: @reason,
|
||
|
status: response.status,
|
||
|
headers: request.headers.to_h.select { |k, _| /\A[A-Z]/ =~ k },
|
||
|
params: params
|
||
|
}
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|