Skip to content

Add C2S media upload (uploadMedia endpoint)#3287

Draft
pfefferle wants to merge 17 commits into
trunkfrom
add/c2s-media-upload
Draft

Add C2S media upload (uploadMedia endpoint)#3287
pfefferle wants to merge 17 commits into
trunkfrom
add/c2s-media-upload

Conversation

@pfefferle
Copy link
Copy Markdown
Member

Fixes #3286

Proposed changes:

Adds an OAuth-protected ActivityPub Client-to-Server uploadMedia endpoint so AP-native clients can push images, audio, and video into the WordPress media library and reference the returned object in a subsequent outbox Create.

  • New REST route POST /activitypub/1.0/actors/{user_id}/uploadMedia (multipart/form-data).
  • New REST route GET /activitypub/1.0/media/{attachment_id} returning the AP-JSON representation of an attachment.
  • New OAuth scope upload (separate from write, mirroring Mastodon's write:media).
  • Accepts both the W3C-SocialCG wiki shape (file + object JSON-LD shell) and the Pleroma shape (file only + optional description form field).
  • Returns 201 Created with a Location header pointing at the new attachment's canonical AP id; always the bare media object (never auto-wraps in Create).
  • Hardened auth: OAuth upload scope, owner check, plus current_user_can('upload_files') so subscribers cannot upload.
  • Orphaned attachment + temp file cleanup on every failure branch (415, 500, transformer failure).
  • Advertised on User and Blog actors via endpoints.uploadMedia, gated behind the existing activitypub_api site option.
  • FEDERATION.md documents the feature; changelog entry included.

References: W3C SocialCG wiki spec, Pleroma uploadMedia, SWICG activitypub-api#6, w3c/activitypub#445.

Other information:

  • Have you written new tests for your changes, if applicable?

17 new tests on Test_Media_Controller covering route registration, GET image/audio/video and 404/415 paths, POST happy path with DB-state verification, missing-file 400, malformed-JSON 400, Pleroma description fallback, object.name precedence, cross-actor 403, missing-cap 403, actor advertisement (User and Blog), and API-disabled gate enforcement. Plus extended coverage on Scope and the new get_attachment_ap_id() helper. Full PHP suite passes locally (2525 tests).

Testing instructions:

Enable the experimental ActivityPub API in Settings → ActivityPub → Advanced.

Smoke-test the GET endpoint:

  1. Upload an image to the media library.
  2. Visit https://<your-site>/wp-json/activitypub/1.0/media/<attachment_id>.
  3. Expect a JSON Image object with type, mediaType, url, and an id matching the request URL.

Smoke-test the POST endpoint (curl):

  1. Get an OAuth Bearer token via the existing OAuth flow, requesting the upload scope.

  2. Run:

    curl -X POST \
      -H "Authorization: Bearer <token>" \
      -F "file=@/path/to/photo.jpg" \
      -F 'object={"type":"Image","name":"my alt text"}' \
      https://<your-site>/wp-json/activitypub/1.0/actors/<user_id>/uploadMedia
  3. Expect 201 Created, a Location: https://.../media/<new_id> header, and a bare AP Image body. The file should appear in the WP media library.

Pleroma-style: omit the object field, send description=alt text instead. Same response.

Negative cases:

  • Omit file400 activitypub_missing_file.
  • Send object={not json400 activitypub_invalid_object.
  • Upload application/pdf415 activitypub_unsupported_media_type.
  • Subscriber-level token → 403 activitypub_cannot_upload.
  • Upload to another user's URL → 403.
  • Disable the ActivityPub API option → route returns 404; endpoints.uploadMedia no longer appears in the actor JSON.

Changelog entry

Entry already lives in .github/changelog/c2s-media-upload (Significance: minor, Type: added). No checkbox needed.

pfefferle added 16 commits May 12, 2026 16:06
Registers two routes: POST uploadMedia (501 placeholder, Task 4) and
GET /media/{attachment_id} which returns the AP-JSON representation
of a WordPress attachment using the existing Base transformer shim.
Register Media_Controller through the existing activitypub_api option
gate in rest_init(), and update the test set_up() to exercise the
production registration path instead of calling register_routes() directly.
Adds a gate test verifying the route is absent when the option is false.
Reuse the same resize-and-WebP optimization the Mastodon importer
already applies, so uploaded images don't bypass it.

- Promote Attachments::optimize_image() to public (was private).
  No callers outside the class change behavior; the only addition is
  one new caller below.
- After wp_handle_upload in Media_Controller::upload_item, run the
  optimization on the saved file. Refresh the cached MIME so the AP
  response advertises the format that actually lives on disk.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Add C2S media upload support (uploadMedia endpoint)

1 participant