mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-12-07 14:09:47 +00:00
fix: add stub outboxes to actors (#10120)
Mastodon doesn't create actors locally if the outbox is not found. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10120 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: famfo <famfo@famfo.xyz> Co-committed-by: famfo <famfo@famfo.xyz>
This commit is contained in:
parent
3f207017a8
commit
b428d47aaa
9 changed files with 178 additions and 37 deletions
15
modules/forgefed/outbox.go
Normal file
15
modules/forgefed/outbox.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
||||
import (
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
// ActivityStream OrderedCollection of activities
|
||||
// swagger:model
|
||||
type ForgeOutbox struct {
|
||||
// swagger:ignore
|
||||
ap.OutboxStream
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ func Actor(ctx *context.APIContext) {
|
|||
actor.URL = ap.IRI(setting.AppURL)
|
||||
|
||||
actor.Inbox = ap.IRI(link + "/inbox")
|
||||
actor.Outbox = ap.IRI(link + "/outbox")
|
||||
actor.PublicKey.ID = ap.IRI(link + "#main-key")
|
||||
actor.PublicKey.Owner = ap.IRI(link)
|
||||
|
||||
|
|
@ -79,3 +80,33 @@ func ActorInbox(ctx *context.APIContext) {
|
|||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func ActorOutbox(ctx *context.APIContext) {
|
||||
// swagger:operation POST /activitypub/actor/outbox activitypub activitypubInstanceActorOutbox
|
||||
// ---
|
||||
// summary: Display the outbox (always empty)
|
||||
// produces:
|
||||
// - application/ld+json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Outbox"
|
||||
|
||||
link := user_model.APServerActorID()
|
||||
outbox := ap.OrderedCollectionNew(ap.IRI(link + "/outbox"))
|
||||
|
||||
binary, err := jsonld.WithContext(
|
||||
jsonld.IRI(ap.ActivityBaseURI),
|
||||
).Marshal(outbox)
|
||||
if err != nil {
|
||||
ctx.ServerError("MarshalJSON", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Resp.Header().Add("Content-Type", activitypub.ActivityStreamsContentType)
|
||||
ctx.Resp.WriteHeader(http.StatusOK)
|
||||
|
||||
_, err = ctx.Resp.Write(binary)
|
||||
if err != nil {
|
||||
log.Error("write to resp err: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ func PersonFeed(ctx *context.APIContext) {
|
|||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/PersonFeed"
|
||||
// "$ref": "#/responses/Outbox"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/modules/activitypub"
|
||||
"forgejo.org/modules/forgefed"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/setting"
|
||||
|
|
@ -16,6 +17,7 @@ import (
|
|||
"forgejo.org/services/federation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
"github.com/go-ap/jsonld"
|
||||
)
|
||||
|
||||
// Repository function returns the Repository actor for a repo
|
||||
|
|
@ -39,6 +41,9 @@ func Repository(ctx *context.APIContext) {
|
|||
link := fmt.Sprintf("%s/api/v1/activitypub/repository-id/%d", strings.TrimSuffix(setting.AppURL, "/"), ctx.Repo.Repository.ID)
|
||||
repo := forgefed.RepositoryNew(ap.IRI(link))
|
||||
|
||||
repo.Inbox = ap.IRI(link + "/inbox")
|
||||
repo.Outbox = ap.IRI(link + "/outbox")
|
||||
|
||||
repo.Name = ap.NaturalLanguageValuesNew()
|
||||
err := repo.Name.Set("en", ap.Content(ctx.Repo.Repository.Name))
|
||||
if err != nil {
|
||||
|
|
@ -81,3 +86,40 @@ func RepositoryInbox(ctx *context.APIContext) {
|
|||
}
|
||||
responseServiceResult(ctx, result)
|
||||
}
|
||||
|
||||
func RepositoryOutbox(ctx *context.APIContext) {
|
||||
// swagger:operation POST /activitypub/repository-id/{repository-id}/outbox activitypub activitypubRepositoryOutbox
|
||||
// ---
|
||||
// summary: Display the outbox
|
||||
// produces:
|
||||
// - application/ld+json
|
||||
// parameters:
|
||||
// - name: repository-id
|
||||
// in: path
|
||||
// description: repository ID of the repo
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Outbox"
|
||||
|
||||
repository := ctx.Repo.Repository
|
||||
outbox := ap.OrderedCollectionNew(ap.IRI(repository.APActorID() + "/outbox"))
|
||||
|
||||
binary, err := jsonld.WithContext(
|
||||
jsonld.IRI(ap.ActivityBaseURI),
|
||||
).Marshal(outbox)
|
||||
if err != nil {
|
||||
ctx.ServerError("MarshalJSON", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Resp.Header().Add("Content-Type", activitypub.ActivityStreamsContentType)
|
||||
ctx.Resp.WriteHeader(http.StatusOK)
|
||||
|
||||
_, err = ctx.Resp.Write(binary)
|
||||
if err != nil {
|
||||
log.Error("write to resp err: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -892,6 +892,7 @@ func Routes() *web.Route {
|
|||
m.Group("/actor", func() {
|
||||
m.Get("", activitypub.Actor)
|
||||
m.Post("/inbox", activitypub.ReqHTTPUserOrInstanceSignature(), activitypub.ActorInbox)
|
||||
m.Get("/outbox", activitypub.ActorOutbox)
|
||||
})
|
||||
m.Group("/repository-id/{repository-id}", func() {
|
||||
m.Get("", activitypub.ReqHTTPUserSignature(), activitypub.Repository)
|
||||
|
|
@ -899,6 +900,7 @@ func Routes() *web.Route {
|
|||
bind(ap.Activity{}),
|
||||
activitypub.ReqHTTPUserSignature(),
|
||||
activitypub.RepositoryInbox)
|
||||
m.Get("/outbox", activitypub.ReqHTTPUserSignature(), activitypub.RepositoryOutbox)
|
||||
}, context.RepositoryIDAssignmentAPI())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
package swagger
|
||||
|
||||
import (
|
||||
"forgejo.org/modules/forgefed"
|
||||
api "forgejo.org/modules/structs"
|
||||
)
|
||||
|
||||
|
|
@ -14,9 +15,9 @@ type swaggerResponseActivityPub struct {
|
|||
Body api.ActivityPub `json:"body"`
|
||||
}
|
||||
|
||||
// Personfeed
|
||||
// swagger:response PersonFeed
|
||||
type swaggerResponsePersonFeed struct {
|
||||
// Outbox
|
||||
// swagger:response Outbox
|
||||
type swaggerResponseOutbox struct {
|
||||
// in:body
|
||||
Body []api.APPersonFollowItem `json:"body"`
|
||||
Body forgefed.ForgeOutbox `json:"body"`
|
||||
}
|
||||
|
|
|
|||
88
templates/swagger/v1_json.tmpl
generated
88
templates/swagger/v1_json.tmpl
generated
|
|
@ -57,6 +57,23 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/activitypub/actor/outbox": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/ld+json"
|
||||
],
|
||||
"tags": [
|
||||
"activitypub"
|
||||
],
|
||||
"summary": "Display the outbox (always empty)",
|
||||
"operationId": "activitypubInstanceActorOutbox",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/Outbox"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/activitypub/repository-id/{repository-id}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
|
@ -118,6 +135,33 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/activitypub/repository-id/{repository-id}/outbox": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/ld+json"
|
||||
],
|
||||
"tags": [
|
||||
"activitypub"
|
||||
],
|
||||
"summary": "Display the outbox",
|
||||
"operationId": "activitypubRepositoryOutbox",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "repository ID of the repo",
|
||||
"name": "repository-id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/Outbox"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/activitypub/user-id/{user-id}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
|
@ -259,7 +303,7 @@
|
|||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/PersonFeed"
|
||||
"$ref": "#/responses/Outbox"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbidden"
|
||||
|
|
@ -21925,28 +21969,6 @@
|
|||
},
|
||||
"x-go-package": "forgejo.org/services/context"
|
||||
},
|
||||
"APPersonFollowItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"actor_id": {
|
||||
"type": "string",
|
||||
"x-go-name": "ActorID"
|
||||
},
|
||||
"note": {
|
||||
"type": "string",
|
||||
"x-go-name": "Note"
|
||||
},
|
||||
"original_item": {
|
||||
"type": "string",
|
||||
"x-go-name": "OriginalItem"
|
||||
},
|
||||
"original_url": {
|
||||
"type": "string",
|
||||
"x-go-name": "OriginalURL"
|
||||
}
|
||||
},
|
||||
"x-go-package": "forgejo.org/modules/structs"
|
||||
},
|
||||
"AccessToken": {
|
||||
"type": "object",
|
||||
"title": "AccessToken represents an API access token.",
|
||||
|
|
@ -25707,6 +25729,11 @@
|
|||
"type": "object",
|
||||
"x-go-package": "forgejo.org/modules/forgefed"
|
||||
},
|
||||
"ForgeOutbox": {
|
||||
"description": "ActivityStream OrderedCollection of activities",
|
||||
"type": "object",
|
||||
"x-go-package": "forgejo.org/modules/forgefed"
|
||||
},
|
||||
"GPGKey": {
|
||||
"description": "GPGKey a user GPG key to sign commit and tag in repository",
|
||||
"type": "object",
|
||||
|
|
@ -30489,6 +30516,12 @@
|
|||
"$ref": "#/definitions/OrganizationPermissions"
|
||||
}
|
||||
},
|
||||
"Outbox": {
|
||||
"description": "Outbox",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForgeOutbox"
|
||||
}
|
||||
},
|
||||
"Package": {
|
||||
"description": "Package",
|
||||
"schema": {
|
||||
|
|
@ -30513,15 +30546,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"PersonFeed": {
|
||||
"description": "Personfeed",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/APPersonFollowItem"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PublicKey": {
|
||||
"description": "PublicKey",
|
||||
"schema": {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package integration
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
|
@ -21,6 +22,7 @@ import (
|
|||
ap "github.com/go-ap/activitypub"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/valyala/fastjson"
|
||||
)
|
||||
|
||||
func TestActivityPubActor(t *testing.T) {
|
||||
|
|
@ -41,6 +43,7 @@ func TestActivityPubActor(t *testing.T) {
|
|||
keyID := actor.GetID().String()
|
||||
assert.Regexp(t, "activitypub/actor$", keyID)
|
||||
assert.Regexp(t, "activitypub/actor/inbox$", actor.Inbox.GetID().String())
|
||||
assert.Regexp(t, "activitypub/actor/outbox$", actor.Outbox.GetID().String())
|
||||
|
||||
pubKey := actor.PublicKey
|
||||
assert.NotNil(t, pubKey)
|
||||
|
|
@ -50,6 +53,28 @@ func TestActivityPubActor(t *testing.T) {
|
|||
pubKeyPem := pubKey.PublicKeyPem
|
||||
assert.NotNil(t, pubKeyPem)
|
||||
assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----", pubKeyPem)
|
||||
|
||||
t.Run("ActorOutboxEmpty", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", actor.Outbox.GetID().String())
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
jsonResp, err := fastjson.ParseBytes(body)
|
||||
require.NoError(t, err)
|
||||
|
||||
outbox := ap.JSONUnmarshalToItem(jsonResp)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, ap.OrderedCollectionType, outbox.GetType())
|
||||
outboxCollection, ok := outbox.(*ap.OrderedCollection)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, uint(0), outboxCollection.TotalItems)
|
||||
assert.Nil(t, outboxCollection.First)
|
||||
assert.Nil(t, outboxCollection.Last)
|
||||
})
|
||||
}
|
||||
|
||||
func TestActorNewFromKeyId(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ func TestActivityPubPerson(t *testing.T) {
|
|||
assert.Equal(t, localUserName, person.PreferredUsername.String())
|
||||
assert.Regexp(t, fmt.Sprintf("activitypub/user-id/%d$", localUserID), person.GetID())
|
||||
assert.Regexp(t, fmt.Sprintf("activitypub/user-id/%d/inbox$", localUserID), person.Inbox.GetID().String())
|
||||
assert.Regexp(t, fmt.Sprintf("activitypub/user-id/%d/outbox$", localUserID), person.Outbox.GetID().String())
|
||||
|
||||
assert.NotNil(t, person.PublicKey)
|
||||
assert.Regexp(t, fmt.Sprintf("activitypub/user-id/%d#main-key$", localUserID), person.PublicKey.ID)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue