diff --git a/app/controllers/api/v1/activity_pub/remote_flags_controller.rb b/app/controllers/api/v1/activity_pub/remote_flags_controller.rb new file mode 100644 index 00000000..23245b8b --- /dev/null +++ b/app/controllers/api/v1/activity_pub/remote_flags_controller.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Api + module V1 + module ActivityPub + # Devuelve los reportes remotos hechos + # + # @todo Verificar la firma. Por ahora no es necesario porque no es + # posible obtener remotamente todos los reportes y se identifican por + # UUIDv4. + class RemoteFlagsController < BaseController + skip_forgery_protection + + def show + render json: (remote_flag&.content || {}), content_type: 'application/activity+json' + end + + private + + # @return [ActivityPub::RemoteFlag,nil] + def remote_flag + @remote_flag ||= ::ActivityPub::RemoteFlag.find(params[:id]) + end + end + end + end +end diff --git a/app/jobs/activity_pub/remote_flag_job.rb b/app/jobs/activity_pub/remote_flag_job.rb new file mode 100644 index 00000000..332d31ac --- /dev/null +++ b/app/jobs/activity_pub/remote_flag_job.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# Envía un reporte directamente a la instancia remota +# +# @todo El panel debería ser su propia instancia y firmar sus propios +# mensajes. +# @todo Como la Social Inbox no soporta enviar actividades +# a destinataries que no sean seguidores, enviamos el reporte +# directamente a la instancia. +# @see {https://github.com/hyphacoop/social.distributed.press/issues/14} +class ActivityPub + class RemoteFlagJob < ApplicationJob + self.priority = 30 + + def perform(remote_flag:) + client = remote_flag.site.social_inbox.client_for(remote_flag.actor.content['inbox']) + response = client.post(endpoint: '', body: remote_flag.content) + + raise 'No se pudo enviar el reporte' unless response.ok? + rescue Exception => e + ExceptionNotifier.notify_exception(e, data: { remote_flag: remote_flag.id, response: response.parsed_response }) + raise + end + end +end diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb index a5171815..fe6052bf 100644 --- a/app/models/activity_pub/actor.rb +++ b/app/models/activity_pub/actor.rb @@ -13,6 +13,7 @@ class ActivityPub has_many :actor_moderation has_many :activity_pubs, as: :object has_many :activities + has_many :remote_flags # Obtiene el nombre de la Actor como mención, solo si obtuvimos el # contenido de antemano. diff --git a/app/models/deploy_social_distributed_press.rb b/app/models/deploy_social_distributed_press.rb index 9f968f36..eec8189b 100644 --- a/app/models/deploy_social_distributed_press.rb +++ b/app/models/deploy_social_distributed_press.rb @@ -58,13 +58,6 @@ class DeploySocialDistributedPress < Deploy private - # Obtiene el hostname de la API de Sutty - # - # @return [String] - def api_hostname - Rails.application.routes.default_url_options[:host].sub('panel', 'api') - end - # Crea los hooks en la Social Inbox para que nos avise de actividades # nuevas # @@ -80,7 +73,7 @@ class DeploySocialDistributedPress < Deploy webhook_class.new.call({ method: 'POST', url: Rails.application.routes.url_helpers.public_send( - event_url, site_id: site.name, host: api_hostname + event_url, site_id: site.name, host: site.social_inbox_hostname ), headers: { 'X-Social-Inbox': rol.token diff --git a/app/models/site/social_distributed_press.rb b/app/models/site/social_distributed_press.rb index e916bf3e..0716a670 100644 --- a/app/models/site/social_distributed_press.rb +++ b/app/models/site/social_distributed_press.rb @@ -15,6 +15,7 @@ class Site has_many :actor_moderations has_many :fediblock_states has_many :instances, through: :instance_moderations + has_many :remote_flags, class_name: 'ActivityPub::RemoteFlag' before_save :generate_private_key_pem!, unless: :private_key_pem? @@ -23,6 +24,13 @@ class Site @social_inbox ||= SocialInbox.new(site: self) end + # Obtiene el hostname de la API de Sutty + # + # @return [String] + def social_inbox_hostname + Rails.application.routes.default_url_options[:host].sub('panel', 'api') + end + private # Genera la llave privada y la almacena diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 6677a320..183ebfb0 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -37,13 +37,25 @@ class SocialInbox # @return [DistributedPress::V1::Social::Client] def client - @client ||= DistributedPress::V1::Social::Client.new( - url: site.config.dig('activity_pub', 'url'), - public_key_url: public_key_url, - private_key_pem: site.private_key_pem, - logger: Rails.logger, - cache_store: HTTParty::Cache::Store::Redis.new(redis_url: ENV['REDIS_SERVER']) - ) + @client ||= client_for site.config.dig('activity_pub', 'url') + end + + # Permite enviar mensajes directo a otro servidor + # + # @param url [String] + # @return [DistributedPress::V1::Social::Client] + def client_for(url) + raise "Falló generar un cliente" if url.blank? + + @client_for ||= {} + @client_for[url] ||= + DistributedPress::V1::Social::Client.new( + url: url, + public_key_url: public_key_url, + private_key_pem: site.private_key_pem, + logger: Rails.logger, + cache_store: HTTParty::Cache::Store::Redis.new(redis_url: ENV['REDIS_SERVER']) + ) end # @return [DistributedPress::V1::Social::Inbox] diff --git a/config/routes.rb b/config/routes.rb index ceffcd26..32936d42 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,6 +11,10 @@ Rails.application.routes.draw do namespace :v1 do resources :csp_reports, only: %i[create] + namespace :activity_pub do + resources :remote_flags, only: %i[show] + end + resources :sites, only: %i[index], constraints: { site_id: /[a-z0-9\-.]+/, id: /[a-z0-9\-.]+/ } do get :'invitades/cookie', to: 'invitades#cookie' post :'posts/:layout', to: 'posts#create', as: :posts diff --git a/db/migrate/20240229201155_create_activity_pub_remote_flags.rb b/db/migrate/20240229201155_create_activity_pub_remote_flags.rb new file mode 100644 index 00000000..c60aca22 --- /dev/null +++ b/db/migrate/20240229201155_create_activity_pub_remote_flags.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Lleva el registro de reportes remotos +class CreateActivityPubRemoteFlags < ActiveRecord::Migration[6.1] + def change + create_table :activity_pub_remote_flags, id: :uuid do |t| + t.timestamps + t.belongs_to :site + t.uuid :actor_id, index: true + + t.text :message + + t.index %i[site_id actor_id], unique: true + end + end +end diff --git a/db/structure.sql b/db/structure.sql index e4d39ad0..6cdb49f1 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -546,6 +546,20 @@ CREATE TABLE public.activity_pub_objects ( ); +-- +-- Name: activity_pub_remote_flags; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.activity_pub_remote_flags ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + site_id bigint, + actor_id uuid, + message text +); + + -- -- Name: activity_pubs; Type: TABLE; Schema: public; Owner: - -- @@ -1762,6 +1776,14 @@ ALTER TABLE ONLY public.activity_pub_objects ADD CONSTRAINT activity_pub_objects_pkey PRIMARY KEY (id); +-- +-- Name: activity_pub_remote_flags activity_pub_remote_flags_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity_pub_remote_flags + ADD CONSTRAINT activity_pub_remote_flags_pkey PRIMARY KEY (id); + + -- -- Name: activity_pubs activity_pubs_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2122,6 +2144,27 @@ CREATE INDEX index_activity_pub_actors_on_uri ON public.activity_pub_actors USIN CREATE INDEX index_activity_pub_instances_on_hostname ON public.activity_pub_instances USING btree (hostname); +-- +-- Name: index_activity_pub_remote_flags_on_actor_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_activity_pub_remote_flags_on_actor_id ON public.activity_pub_remote_flags USING btree (actor_id); + + +-- +-- Name: index_activity_pub_remote_flags_on_site_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_activity_pub_remote_flags_on_site_id ON public.activity_pub_remote_flags USING btree (site_id); + + +-- +-- Name: index_activity_pub_remote_flags_on_site_id_and_actor_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_activity_pub_remote_flags_on_site_id_and_actor_id ON public.activity_pub_remote_flags USING btree (site_id, actor_id); + + -- -- Name: index_activity_pubs_on_site_id_and_object_id_and_object_type; Type: INDEX; Schema: public; Owner: - -- @@ -2653,6 +2696,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240227134845'), ('20240227142019'), ('20240228171335'), -('20240228202830'); +('20240228202830'), +('20240229201155');