ADD controller for ActivityPub subscriptions to releases actor
⚠ ️ This is the third and last part of the merging of the overarching MR, please read its description if you want to understand what is happening here. :) Feel free to ask as many questions as you want if you want to understand the protocol!
What does this MR do and why?
This is the third and last part of the merging the overarching subscription MR.
This provides the controller, route and service used to create subscriptions.
We add the inbox
endpoint as specified by ActivityPub on our
releases
actor to receive various post requests. The only requests we
accept are Follow and unfollow (Undo > Follow) ones. If we receive an
other kind of activity (that could be, for example, that someone
commented on our release post in the Fediverse), we silently discard it.
The service's role is to create the blank subscription and to queue the background job that will resolve the various URLs and prepare the subscription.
How to set up and validate locally
A Sinatra app has been created to test communication with the application, as this is a server to server feature:
- prepare your GitLab local installation by making the
flightjs/Fligthjs
project public, enabling theactivity_pub
andactivity_pub_project
feature flags and allowing requests to the local network from webhooks and integrations. This can be done in a rails console with the following:
Feature.enable(:activity_pub)
Feature.enable(:activity_pub_project)
ApplicationSetting.first.update(allow_local_requests_from_web_hooks_and_services: true)
flight = Project.find_by_name("Flight")
flight.update(visibility: "public")
- Install and run the sinatra app:
git clone https://gitlab.com/oelmekki/activitypub-review
cd activitypub-review
bundle
ruby server.rb
- Visit the app URL and click the "subscribe" button
- You should see the success reply from GitLab (it may takes a while on first request in dev env)
- Go check the Sinatra logs, you should see:
- a request to
/subscriber/inbox
, this is the GitLab server sending the Accept activity - a dump of the request object, you can verify it correctly sets the Accept http header to
application/ld+json; profile="https://www.w3.org/ns/activitystreams"
- a dump of the activity posted, as a JSON object, with type "Accept" and containing the initial Follow activity as object.
⚠ ️ Note: if you want to run the test several times, you need to delete the subscription between each time:ActivityPub::ReleasesSubscription.destroy_all
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.
Query plans
Requests are all in the service.
ReleasesSubscription.find_by_subscriber_url(subscriber_url)
SELECT "activity_pub_releases_subscriptions".*
FROM "activity_pub_releases_subscriptions"
WHERE (LOWER(subscriber_url) = 'https://example.com/new-actor')
LIMIT 1
Limit (cost=0.15..3.73 rows=1 width=162)
-> Index Scan using index_activity_pub_releases_sub_on_project_id_sub_url on activity_pub_releases_subscriptions (cost=0.15..7.32 rows=2 width=162)
Index Cond: (lower(subscriber_url) = 'https://example.com/new-actor'::text)
subscription.save
This generates 3 queries.
SELECT 1 AS one
FROM "activity_pub_releases_subscriptions"
WHERE LOWER("activity_pub_releases_subscriptions"."subscriber_url") = LOWER('https://example.com/new-actor')
AND "activity_pub_releases_subscriptions"."project_id" = 1
LIMIT 1
Limit (cost=0.15..2.17 rows=1 width=4)
-> Index Scan using index_activity_pub_releases_sub_on_project_id_sub_url on activity_pub_releases_subscriptions (cost=0.15..2.17 rows=1 width=4)
Index Cond: ((project_id = 1) AND (lower(subscriber_url) = 'https://example.com/new-actor'::text))
There are tons of fields selected here, let me know if you want the details (this is not something I implemented, it comes from Release
model callbacks).
SELECT [...]
FROM "application_settings"
ORDER BY "application_settings"."id" DESC
LIMIT 1
Limit (cost=0.12..2.14 rows=1 width=12592)
-> Index Scan Backward using application_settings_pkey on application_settings (cost=0.12..2.14 rows=1 width=12592)
INSERT INTO "activity_pub_releases_subscriptions" ("project_id", "created_at", "updated_at", "status", "subscriber_url", "payload")
VALUES (1, '2023-10-26 09:23:15.057518', '2023-10-26 09:23:15.057518', 0, 'https://example.com/new-actor', '{"@context":"https://www.w3.org/ns/activitystreams","id":"https://example.com/new-actor#follow-1","type":"Follow","actor":"https://example.com/new-actor","object":"https://localhost/our/project/-/releases"}')
RETURNING "id"
Insert on activity_pub_releases_subscriptions (cost=0.00..0.01 rows=1 width=162)
-> Result (cost=0.00..0.01 rows=1 width=162)
previous_subscription.destroy
DELETE FROM "activity_pub_releases_subscriptions"
WHERE "activity_pub_releases_subscriptions"."id" = 1
Delete on activity_pub_releases_subscriptions (cost=0.15..2.17 rows=0 width=0)
-> Index Scan using activity_pub_releases_subscriptions_pkey on activity_pub_releases_subscriptions (cost=0.15..2.17 rows=1 width=6)
Index Cond: (id = 1)
Related to epic &11247