activitypub: Implement an instance-wide actor

An instance-wide actor is required for outgoing signed requests that are
done on behalf of the instance, rather than on behalf of other actors.
Such things include updating profile information, or fetching public
keys.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
This commit is contained in:
Gergely Nagy 2024-08-05 10:50:26 +02:00
parent cd17eb0fa7
commit f121e87aa6
No known key found for this signature in database
5 changed files with 198 additions and 0 deletions

View file

@ -0,0 +1,83 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package activitypub
import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context"
ap "github.com/go-ap/activitypub"
"github.com/go-ap/jsonld"
)
// Actor function returns the instance's Actor
func Actor(ctx *context.APIContext) {
// swagger:operation GET /activitypub/actor activitypub activitypubInstanceActor
// ---
// summary: Returns the instance's Actor
// produces:
// - application/json
// responses:
// "200":
// "$ref": "#/responses/ActivityPub"
link := user_model.APActorUserAPActorID()
actor := ap.ActorNew(ap.IRI(link), ap.ApplicationType)
actor.PreferredUsername = ap.NaturalLanguageValuesNew()
err := actor.PreferredUsername.Set("en", ap.Content(setting.Domain))
if err != nil {
ctx.ServerError("PreferredUsername.Set", err)
return
}
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)
publicKeyPem, err := activitypub.GetPublicKey(ctx, user_model.NewAPActorUser())
if err != nil {
ctx.ServerError("GetPublicKey", err)
return
}
actor.PublicKey.PublicKeyPem = publicKeyPem
binary, err := jsonld.WithContext(
jsonld.IRI(ap.ActivityBaseURI),
jsonld.IRI(ap.SecurityContextURI),
).Marshal(actor)
if err != nil {
ctx.ServerError("MarshalJSON", err)
return
}
ctx.Resp.Header().Add("Content-Type", activitypub.ActivityStreamsContentType)
ctx.Resp.WriteHeader(http.StatusOK)
if _, err = ctx.Resp.Write(binary); err != nil {
log.Error("write to resp err: %v", err)
}
}
// ActorInbox function handles the incoming data for the instance Actor
func ActorInbox(ctx *context.APIContext) {
// swagger:operation POST /activitypub/actor/inbox activitypub activitypubInstanceActorInbox
// ---
// summary: Send to the inbox
// produces:
// - application/json
// responses:
// "204":
// "$ref": "#/responses/empty"
ctx.Status(http.StatusNoContent)
}

View file

@ -805,6 +805,10 @@ func Routes() *web.Route {
m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
}, context.UserIDAssignmentAPI())
m.Group("/actor", func() {
m.Get("", activitypub.Actor)
m.Post("/inbox", activitypub.ActorInbox)
})
m.Group("/repository-id/{repository-id}", func() {
m.Get("", activitypub.Repository)
m.Post("/inbox",