5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2025-03-15 03:38:17 +00:00

Merge branch 'issue-15109-1' of https://0xacab.org/sutty/sutty into production.panel.sutty.nl

This commit is contained in:
Sutty 2024-03-12 18:57:36 +00:00
commit 67a096b015
12 changed files with 146 additions and 113 deletions

View file

@ -167,7 +167,7 @@ GEM
devise_invitable (2.0.9) devise_invitable (2.0.9)
actionmailer (>= 5.0) actionmailer (>= 5.0)
devise (>= 4.6) devise (>= 4.6)
distributed-press-api-client (0.4.0rc3) distributed-press-api-client (0.4.0)
addressable (~> 2.3, >= 2.3.0) addressable (~> 2.3, >= 2.3.0)
climate_control climate_control
dry-schema dry-schema
@ -627,7 +627,7 @@ DEPENDENCIES
devise devise
devise-i18n devise-i18n
devise_invitable devise_invitable
distributed-press-api-client (~> 0.4.0rc3) distributed-press-api-client (~> 0.4.0)
dotenv-rails dotenv-rails
down down
ed25519 ed25519

View file

@ -27,7 +27,7 @@ class ActivityPubsController < ApplicationController
authorize activity_pubs authorize activity_pubs
action = params[:activity_pub_action].to_sym action = params[:activity_pub_action].to_sym
method = :"#{action}!" method = :"#{action}_all!"
may = :"may_#{action}?" may = :"may_#{action}?"
redirect_to_moderation_queue! redirect_to_moderation_queue!
@ -35,36 +35,25 @@ class ActivityPubsController < ApplicationController
return unless ActivityPub.events.include? action return unless ActivityPub.events.include? action
# Crear una sola remote flag por autore # Crear una sola remote flag por autore
if action == :report
message = remote_flag_params(activity_pubs.first).dig(:remote_flag_attributes, :message)
activity_pubs.distinct.pluck(:actor_id).each do |actor_id|
remote_flag = ActivityPub::RemoteFlag.find_or_initialize_by(actor_id: actor_id, site_id: site.id)
remote_flag.message = message
# Lo estamos actualizando, con lo que lo vamos a volver a enviar
remote_flag.requeue if remote_flag.persisted?
remote_flag.save
# XXX: Idealmente todas las ActivityPub que enviamos pueden
# cambiar de estado, pero chequeamos de todas formas.
remote_flag.activity_pubs << (activity_pubs.where(actor_id: actor_id).to_a.select { |a| a.public_send(may) })
end
end
ActivityPub.transaction do ActivityPub.transaction do
activity_pubs.find_each do |activity_pub| if action == :report
next unless activity_pub.public_send(may) message = remote_flag_params(activity_pubs.first).dig(:remote_flag_attributes, :message)
activity_pub.public_send(method) activity_pubs.distinct.pluck(:actor_id).each do |actor_id|
remote_flag = ActivityPub::RemoteFlag.find_or_initialize_by(actor_id: actor_id, site_id: site.id)
flash[:success] = I18n.t('activity_pubs.action_on_several.success') remote_flag.message = message
rescue Exception => e # Lo estamos actualizando, con lo que lo vamos a volver a enviar
ExceptionNotifier.notify_exception(e, remote_flag.requeue if remote_flag.persisted?
data: { site: site.name, params: params.permit!.to_h, remote_flag.save
activity_pub: activity_pub.id }) # XXX: Idealmente todas las ActivityPub que enviamos pueden
# cambiar de estado, pero chequeamos de todas formas.
flash.delete(:success) remote_flag.activity_pubs << (activity_pubs.where(actor_id: actor_id).to_a.select { |a| a.public_send(may) })
flash[:error] = I18n.t('activity_pubs.action_on_several.error') end
end end
message = activity_pubs.public_send(method) ? :success : :error
flash[message] = I18n.t("activity_pubs.action_on_several.#{message}")
end end
end end

View file

@ -37,7 +37,7 @@ class ActorModerationsController < ApplicationController
authorize actor_moderations authorize actor_moderations
action = params[:actor_moderation_action].to_sym action = params[:actor_moderation_action].to_sym
method = :"#{action}!" method = :"#{action}_all!"
may = :"may_#{action}?" may = :"may_#{action}?"
redirect_to_moderation_queue! redirect_to_moderation_queue!
@ -45,20 +45,17 @@ class ActorModerationsController < ApplicationController
return unless ActorModeration.events.include? action return unless ActorModeration.events.include? action
ActorModeration.transaction do ActorModeration.transaction do
actor_moderations.find_each do |actor_moderation| if action == :report
next unless actor_moderation.public_send(may) actor_moderations.find_each do |actor_moderation|
next unless actor_moderation.public_send(may)
actor_moderation.update(actor_moderation_params(actor_moderation)) if action == :report actor_moderation.update(actor_moderation_params(actor_moderation))
end
actor_moderation.public_send(method)
flash[:success] = I18n.t('actor_moderations.action_on_several.success')
rescue Exception => e
ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params.permit!.to_h })
flash.delete(:success)
flash[:error] = I18n.t('actor_moderations.action_on_several.error')
end end
message = actor_moderation.public_send(method) ? :success : :error
flash[message] = I18n.t("actor_moderations.action_on_several.#{message}")
end end
end end

View file

@ -10,6 +10,12 @@ class InstanceModerationsController < ApplicationController
instance_moderation.public_send(:"#{event}!") if instance_moderation.public_send(:"may_#{event}?") instance_moderation.public_send(:"#{event}!") if instance_moderation.public_send(:"may_#{event}?")
flash[:success] = I18n.t("instance_moderations.#{event}.success")
rescue Exception => e
ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params.permit!.to_h })
flash[:error] = I18n.t("instance_moderations.#{event}.error")
ensure
redirect_to_moderation_queue! redirect_to_moderation_queue!
end end
end end
@ -20,17 +26,16 @@ class InstanceModerationsController < ApplicationController
authorize instance_moderations authorize instance_moderations
action = params[:instance_moderation_action].to_sym action = params[:instance_moderation_action].to_sym
method = :"#{action}!" method = :"#{action}_all!"
may = :"may_#{action}?"
redirect_to_moderation_queue! redirect_to_moderation_queue!
return unless InstanceModeration.events.include? action return unless InstanceModeration.events.include? action
InstanceModeration.transaction do InstanceModeration.transaction do
instance_moderations.find_each do |instance_moderation| message = instance_moderations.public_send(method) ? :success : :error
instance_moderation.public_send(method) if instance_moderation.public_send(may)
end flash[:message] = I18n.t("instance_moderations.action_on_several.#{message}")
end end
end end

View file

@ -14,7 +14,6 @@ class ActivityPub
end end
instances = ActivityPub::Instance.where(hostname: hostnames) instances = ActivityPub::Instance.where(hostname: hostnames)
success = true
Site.transaction do Site.transaction do
# Crea todas las moderaciones de instancia con un estado por # Crea todas las moderaciones de instancia con un estado por
@ -22,17 +21,11 @@ class ActivityPub
instances.find_each do |instance| instances.find_each do |instance|
# Esto bloquea cada una individualmente en la Social Inbox, # Esto bloquea cada una individualmente en la Social Inbox,
# idealmente son pocas instancias las que aparecen. # idealmente son pocas instancias las que aparecen.
site.instance_moderations.find_or_create_by(instance: instance).tap do |instance_moderation| site.instance_moderations.find_or_create_by(instance: instance)
instance_moderation.block! if instance_moderation.may_block?
# Notificar todos los errores sin detener la ejecución
rescue Exception => e
ExceptionNotifier.notify_exception(e, data: { site: site.name, instance_moderation: instance_moderation.id })
success = false
end
end end
end
success site.instance_moderations.where(instance_id: instances.ids).block_all!
end
end end
end end
end end

View file

@ -8,12 +8,12 @@
# #
# @see {https://www.w3.org/TR/activitypub/#client-to-server-interactions} # @see {https://www.w3.org/TR/activitypub/#client-to-server-interactions}
class ActivityPub < ApplicationRecord class ActivityPub < ApplicationRecord
include AASM
include AasmEventsConcern
IGNORED_EVENTS = %i[remove] IGNORED_EVENTS = %i[remove]
IGNORED_STATES = %i[removed] IGNORED_STATES = %i[removed]
include AASM
include AasmEventsConcern
belongs_to :instance belongs_to :instance
belongs_to :site belongs_to :site
belongs_to :object, polymorphic: true belongs_to :object, polymorphic: true
@ -68,7 +68,7 @@ class ActivityPub < ApplicationRecord
transitions from: %i[paused], to: :approved transitions from: %i[paused], to: :approved
before do before do
raise unless site.social_inbox.inbox.accept(id: object.uri).ok? allow_remotely!
end end
end end
@ -77,7 +77,7 @@ class ActivityPub < ApplicationRecord
transitions from: %i[paused approved], to: :rejected transitions from: %i[paused approved], to: :rejected
before do before do
raise unless site.social_inbox.inbox.reject(id: object.uri).ok? reject_remotely!
end end
end end
@ -90,4 +90,12 @@ class ActivityPub < ApplicationRecord
end end
end end
end end
def reject_remotely!
raise unless site.social_inbox.inbox.reject(id: object.uri).ok?
end
def allow_remotely!
raise unless site.social_inbox.inbox.accept(id: object.uri).ok?
end
end end

View file

@ -2,31 +2,18 @@
# Mantiene la relación entre Site y Actor # Mantiene la relación entre Site y Actor
class ActorModeration < ApplicationRecord class ActorModeration < ApplicationRecord
include AASM
include AasmEventsConcern
IGNORED_EVENTS = %i[remove] IGNORED_EVENTS = %i[remove]
IGNORED_STATES = %i[removed] IGNORED_STATES = %i[removed]
include AASM
include AasmEventsConcern
belongs_to :site belongs_to :site
belongs_to :remote_flag, optional: true, class_name: 'ActivityPub::RemoteFlag' belongs_to :remote_flag, optional: true, class_name: 'ActivityPub::RemoteFlag'
belongs_to :actor, class_name: 'ActivityPub::Actor' belongs_to :actor, class_name: 'ActivityPub::Actor'
accepts_nested_attributes_for :remote_flag accepts_nested_attributes_for :remote_flag
# Bloquea todes les Actores bloqueables
def self.block_all!
self.update_all(aasm_state: 'blocked', updated_at: Time.now)
end
def self.pause_all!
self.update_all(aasm_state: 'paused', updated_at: Time.now)
end
def self.remove_all!
self.update_all(aasm_state: 'removed', updated_at: Time.now)
end
aasm do aasm do
state :paused, initial: true state :paused, initial: true
state :allowed state :allowed
@ -42,19 +29,27 @@ class ActorModeration < ApplicationRecord
end end
end end
# Al permitir una cuenta se permiten todos los comentarios
# pendientes de moderación que ya hizo.
event :allow do event :allow do
transitions from: %i[paused blocked reported], to: :allowed transitions from: %i[paused blocked reported], to: :allowed
before do before do
allow_remotely! allow_remotely!
site.activity_pubs.paused.where(actor_id: self.actor_id).allow_all!
end end
end end
# Al bloquear una cuenta se bloquean todos los comentarios
# pendientes de moderación que hizo.
event :block do event :block do
transitions from: %i[paused allowed], to: :blocked transitions from: %i[paused allowed], to: :blocked
before do before do
block_remotely! block_remotely!
site.activity_pubs.paused.where(actor_id: self.actor_id).reject_all!
end end
end end
@ -72,6 +67,10 @@ class ActorModeration < ApplicationRecord
# mostrarlo y todas sus actividades. # mostrarlo y todas sus actividades.
event :remove do event :remove do
transitions to: :removed transitions to: :removed
before do
site.activity_pubs.where(actor_id: self.actor_id).remove_all!
end
end end
end end

View file

@ -27,5 +27,33 @@ module AasmEventsConcern
def self.states def self.states
aasm.states.map(&:name) - self::IGNORED_STATES aasm.states.map(&:name) - self::IGNORED_STATES
end end
# Define un método que cambia el estado para todos los objetos del
# scope actual.
#
# @return [Bool] Si hubo al menos un error, devuelve false.
self.events.each do |event|
define_singleton_method(:"#{event}_all!") do
success = true
self.find_each do |object|
object.public_send(:"#{event}!") if object.public_send(:"may_#{event}?")
rescue Exception => e
success = false
notify_exception! e, object
end
success
end
end
# Envía notificación de errores
#
# @param exception [Exception]
# @param record [ApplicationRecord]
def notify_exception!(exception, record)
ExceptionNotifier.notify_exception(exception, data: { site: site.name, record_type: record.class.name, record_id: record.id })
end
end end
end end

View file

@ -39,12 +39,6 @@ class FediblockState < ApplicationRecord
# Luego esta tarea crea las que falten e ignora las que ya se # Luego esta tarea crea las que falten e ignora las que ya se
# bloquearon. # bloquearon.
ActivityPub::InstanceModerationJob.perform_now(site: site, hostnames: fediblock.hostnames) ActivityPub::InstanceModerationJob.perform_now(site: site, hostnames: fediblock.hostnames)
# Bloquear a todes les Actores de las instancias bloqueadas para
# indicarle a le usuarie que les tiene que desbloquear
# manualmente.
actor_ids = ActivityPub::Actor.where(instance_id: instance_ids).ids
ActorModeration.where(actor_id: actor_ids).paused.block_all!
end end
end end
@ -66,12 +60,6 @@ class FediblockState < ApplicationRecord
# bloqueadas por otros fediblocks. # bloqueadas por otros fediblocks.
instance_ids = ActivityPub::Instance.where(hostname: unique_hostnames).ids instance_ids = ActivityPub::Instance.where(hostname: unique_hostnames).ids
site.instance_moderations.where(instance_id: instance_ids).pause_all! site.instance_moderations.where(instance_id: instance_ids).pause_all!
# Volver a pausar todes les actores de esta instancia que fueron
# bloqueades, a menos que hayan sido bloqueades por otro
# fediblock.
actor_ids = ActivityPub::Actor.where(instance_id: instance_ids).ids
ActorModeration.where(actor_id: actor_ids).blocked.pause_all!
end end
end end
end end

View file

@ -2,35 +2,22 @@
# Mantiene el registro de relaciones entre sitios e instancias # Mantiene el registro de relaciones entre sitios e instancias
class InstanceModeration < ApplicationRecord class InstanceModeration < ApplicationRecord
include AASM
include AasmEventsConcern
IGNORED_EVENTS = [] IGNORED_EVENTS = []
IGNORED_STATES = [] IGNORED_STATES = []
include AASM
include AasmEventsConcern
belongs_to :site belongs_to :site
belongs_to :instance, class_name: 'ActivityPub::Instance' belongs_to :instance, class_name: 'ActivityPub::Instance'
# Traer todas las instancias bloqueables, según la máquina de estados,
# todas las que no estén bloqueadas ya.
scope :may_block, -> { where.not(aasm_state: 'blocked') }
scope :may_pause, -> { where.not(aasm_state: 'paused') }
# Bloquear instancias en masa
def self.block_all!
self.may_block.update_all(aasm_state: 'blocked', updated_at: Time.now)
end
# Pausar instancias en masa
def self.pause_all!
self.may_pause.update_all(aasm_state: 'paused', updated_at: Time.now)
end
aasm do aasm do
state :paused, initial: true state :paused, initial: true
state :allowed state :allowed
state :blocked state :blocked
# Al volver la instancia a pausa no cambiamos el estado de
# moderación de actores pre-existente.
event :pause do event :pause do
transitions from: %i[allowed blocked], to: :paused transitions from: %i[allowed blocked], to: :paused
@ -39,23 +26,36 @@ class InstanceModeration < ApplicationRecord
end end
end end
# Al permitir, también permitimos todes les actores que no hayan
# tenido acciones de moderación.
event :allow do event :allow do
transitions from: %i[paused blocked], to: :allowed transitions from: %i[paused blocked], to: :allowed
before do before do
allow_remotely! allow_remotely!
site.actor_moderations.paused.where(actor_id: actor_ids).allow_all!
end end
end end
# Al bloquear, también bloqueamos a todes les actores que no hayan
# tenido acciones de moderación.
event :block do event :block do
transitions from: %i[paused allowed], to: :blocked transitions from: %i[paused allowed], to: :blocked
before do before do
block_remotely! block_remotely!
site.actor_moderations.paused.where(actor_id: actor_ids).block_all!
end end
end end
end end
# @return [Array<String>]
def actor_ids
ActivityPub::Actor.where(instance_id: self.instance_id).ids
end
# Elimina la instancia de todas las listas # Elimina la instancia de todas las listas
# #
# @return [Boolean] # @return [Boolean]

View file

@ -138,14 +138,27 @@ en:
success: "Account paused. All of their comments will need to be moderated individually on the Comments section." success: "Account paused. All of their comments will need to be moderated individually on the Comments section."
error: "There was an error while pausing the account. We received a report and will be acting on it soon." error: "There was an error while pausing the account. We received a report and will be acting on it soon."
allow: allow:
success: "Account allowed. All of their comments will be approved automatically." success: "Account allowed. All of their comments will be approved automatically. Any pending comments have been approved."
error: "There was an error while allowing the account. We received a report and will be acting on it soon." error: "There was an error while allowing the account. We received a report and will be acting on it soon."
block: block:
success: "Account blocked. All of their comments will be rejected automatically. If you want to report it to their instance, please use the Report button." success: "Account blocked. All of their comments will be rejected automatically. Any pending comments have been rejected. If you want to report it to their instance, please use the Report button."
error: "There was an error while blocking the account. We received a report and will be acting on it soon." error: "There was an error while blocking the account. We received a report and will be acting on it soon."
report: report:
success: "Account reported." success: "Account reported."
error: "There was an error while reporting the account. We received a report and will be acting on it soon." error: "There was an error while reporting the account. We received a report and will be acting on it soon."
instance_moderations:
action_on_several:
success: "Several instances have changed moderation state. You can find them using the filters on the Instances section."
error: "There was an error while changing moderation state. We received a report and will be acting on it soon."
pause:
success: "Instance paused. All of their comments and accounts will need to be moderated individually."
error: "There was an error while pausing the instance. We received a report and will be acting on it soon."
allow:
success: "Instance allowed. All of their comments and accounts will be approved automatically. Any pending comments and accounts have been also approved."
error: "There was an error while allowing the instance. We received a report and will be acting on it soon."
block:
success: "Instance blocked. All of their comments and accounts will be rejected automatically. Any pending comments and accounts have been also rejected."
error: "There was an error while blocking the instance. We received a report and will be acting on it soon."
fediblock_states: fediblock_states:
action_on_several: action_on_several:
success: "Blocklists have been enabled, you can find their instances by filtering by Blocked. Any pending account from these instances has also been blocked. You can approve them individually on the Accounts section." success: "Blocklists have been enabled, you can find their instances by filtering by Blocked. Any pending account from these instances has also been blocked. You can approve them individually on the Accounts section."

View file

@ -137,14 +137,27 @@ es:
success: "Cuenta pausada. Todos los comentarios que haga necesitan ser aprobados manualmente en la sección Comentarios." success: "Cuenta pausada. Todos los comentarios que haga necesitan ser aprobados manualmente en la sección Comentarios."
error: "No se pudo pausar la cuenta. Hemos recibido el reporte y lo estaremos verificando." error: "No se pudo pausar la cuenta. Hemos recibido el reporte y lo estaremos verificando."
allow: allow:
success: "Cuenta permitida. Todos los comentarios que haga serán aprobados inmediatamente." success: "Cuenta permitida. Todos los comentarios que haga serán aprobados inmediatamente. Todos los comentarios pendientes de moderación fueron aprobados."
error: "No se pudo permitir la cuenta. Hemos recibido el reporte y lo estaremos verificando." error: "No se pudo permitir la cuenta. Hemos recibido el reporte y lo estaremos verificando."
block: block:
success: "Cuenta bloqueada. Todos los comentarios que haga serán rechazados inmediatamente. Si querés reportarla a su instancia, podés usar el botón Reportar." success: "Cuenta bloqueada. Todos los comentarios que haga serán rechazados inmediatamente. Todos los comentarios pendientes de moderación fueron rechazados. Si querés reportarla a su instancia, podés usar el botón Reportar."
error: "No se pudo bloquear la cuenta. Hemos recibido el reporte y lo estaremos verificando." error: "No se pudo bloquear la cuenta. Hemos recibido el reporte y lo estaremos verificando."
report: report:
success: "Cuenta reportada a su instancia." success: "Cuenta reportada a su instancia."
error: "No se pudo reportar la cuenta. Hemos recibido el reporte y lo estaremos verificando." error: "No se pudo reportar la cuenta. Hemos recibido el reporte y lo estaremos verificando."
instance_moderations:
action_on_several:
success: "Se ha modificado el estado de moderación de varias instancias. Podés encontrarlas usando los filtros en la sección Instancias."
error: "Hubo un error al modificar el estado de moderación de varias instancias. Hemos recibido el reporte y lo estaremos verificando."
pause:
success: "Instancia pausada. Todos los comentarios y cuentas de esta instancia necesitan ser aprobados manualmente. No se ha modificado el estado de moderación de cuentas y comentarios pre-existentes."
error: "No se pudo pausar la instancia. Hemos recibido el reporte y lo estaremos verificando."
allow:
success: "Instancia permitida. Todos los comentarios y cuentas pendientes de moderación fueron aprobados y los próximos serán aprobados inmediatamente."
error: "No se pudo permitir la instancia. Hemos recibido el reporte y lo estaremos verificando."
block:
success: "Instancia bloqueada. Todos los comentarios y cuentas serán rechazados inmediatamente. Todos los comentarios y cuentas pendientes de moderación fueron rechazados."
error: "No se pudo bloquear la instancia. Hemos recibido el reporte y lo estaremos verificando."
fediblock_states: fediblock_states:
action_on_several: action_on_several:
success: "Se habilitaron las listas de bloqueo, podés encontrar las instancias filtrando por Bloqueadas. Todas las cuentas de estas instancias pendientes de moderación han sido bloqueadas. Podés activarlas individualmente en la sección Cuentas." success: "Se habilitaron las listas de bloqueo, podés encontrar las instancias filtrando por Bloqueadas. Todas las cuentas de estas instancias pendientes de moderación han sido bloqueadas. Podés activarlas individualmente en la sección Cuentas."