Mark unused&immature activitypub as "not implemented" (#36789)
After many years, "activitypub" is still "in progress" and no real progress for end users. So it is not mature. Temporarily mark the endpoints as "501 not implemented", and wait until the whole design is stable and usable.
This commit is contained in:
@@ -4,103 +4,11 @@
|
||||
package activitypub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// Person function returns the Person actor for a user
|
||||
func Person(ctx *context.APIContext) {
|
||||
// swagger:operation GET /activitypub/user-id/{user-id} activitypub activitypubPerson
|
||||
// ---
|
||||
// summary: Returns the Person actor for a user
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: user-id
|
||||
// in: path
|
||||
// description: user ID of the user
|
||||
// type: integer
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/ActivityPub"
|
||||
|
||||
// TODO: the setting.AppURL during the test doesn't follow the definition: "It always has a '/' suffix"
|
||||
link := fmt.Sprintf("%s/api/v1/activitypub/user-id/%d", strings.TrimSuffix(setting.AppURL, "/"), ctx.ContextUser.ID)
|
||||
person := ap.PersonNew(ap.IRI(link))
|
||||
|
||||
person.Name = ap.NaturalLanguageValuesNew()
|
||||
err := person.Name.Set("en", ap.Content(ctx.ContextUser.FullName))
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
person.PreferredUsername = ap.NaturalLanguageValuesNew()
|
||||
err = person.PreferredUsername.Set("en", ap.Content(ctx.ContextUser.Name))
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
person.URL = ap.IRI(ctx.ContextUser.HTMLURL(ctx))
|
||||
|
||||
person.Icon = ap.Image{
|
||||
Type: ap.ImageType,
|
||||
MediaType: "image/png",
|
||||
URL: ap.IRI(ctx.ContextUser.AvatarLink(ctx)),
|
||||
}
|
||||
|
||||
person.Inbox = ap.IRI(link + "/inbox")
|
||||
person.Outbox = ap.IRI(link + "/outbox")
|
||||
|
||||
person.PublicKey.ID = ap.IRI(link + "#main-key")
|
||||
person.PublicKey.Owner = ap.IRI(link)
|
||||
|
||||
publicKeyPem, err := activitypub.GetPublicKey(ctx, ctx.ContextUser)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
person.PublicKey.PublicKeyPem = publicKeyPem
|
||||
|
||||
binary, err := jsonld.WithContext(jsonld.IRI(ap.ActivityBaseURI), jsonld.IRI(ap.SecurityContextURI)).Marshal(person)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(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)
|
||||
}
|
||||
}
|
||||
|
||||
// PersonInbox function handles the incoming data for a user inbox
|
||||
func PersonInbox(ctx *context.APIContext) {
|
||||
// swagger:operation POST /activitypub/user-id/{user-id}/inbox activitypub activitypubPersonInbox
|
||||
// ---
|
||||
// summary: Send to the inbox
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: user-id
|
||||
// in: path
|
||||
// description: user ID of the user
|
||||
// type: integer
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
func NotImplemented(ctx *context.APIContext) {
|
||||
http.Error(ctx.Resp, "Not implemented", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package activitypub
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"code.gitea.io/gitea/modules/activitypub"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
gitea_context "code.gitea.io/gitea/services/context"
|
||||
|
||||
"github.com/42wim/httpsig"
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
func getPublicKeyFromResponse(b []byte, keyID *url.URL) (p crypto.PublicKey, err error) {
|
||||
person := ap.PersonNew(ap.IRI(keyID.String()))
|
||||
err = person.UnmarshalJSON(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ActivityStreams type cannot be converted to one known to have publicKey property: %w", err)
|
||||
}
|
||||
pubKey := person.PublicKey
|
||||
if pubKey.ID.String() != keyID.String() {
|
||||
return nil, fmt.Errorf("cannot find publicKey with id: %s in %s", keyID, string(b))
|
||||
}
|
||||
pubKeyPem := pubKey.PublicKeyPem
|
||||
block, _ := pem.Decode([]byte(pubKeyPem))
|
||||
if block == nil || block.Type != "PUBLIC KEY" {
|
||||
return nil, errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type")
|
||||
}
|
||||
p, err = x509.ParsePKIXPublicKey(block.Bytes)
|
||||
return p, err
|
||||
}
|
||||
|
||||
func fetch(iri *url.URL) (b []byte, err error) {
|
||||
req := httplib.NewRequest(iri.String(), http.MethodGet)
|
||||
req.Header("Accept", activitypub.ActivityStreamsContentType)
|
||||
req.Header("User-Agent", "Gitea/"+setting.AppVer)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("url IRI fetch [%s] failed with status (%d): %s", iri, resp.StatusCode, resp.Status)
|
||||
}
|
||||
b, err = io.ReadAll(io.LimitReader(resp.Body, setting.Federation.MaxSize))
|
||||
return b, err
|
||||
}
|
||||
|
||||
func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, err error) {
|
||||
r := ctx.Req
|
||||
|
||||
// 1. Figure out what key we need to verify
|
||||
v, err := httpsig.NewVerifier(r)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ID := v.KeyId()
|
||||
idIRI, err := url.Parse(ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// 2. Fetch the public key of the other actor
|
||||
b, err := fetch(idIRI)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
pubKey, err := getPublicKeyFromResponse(b, idIRI)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// 3. Verify the other actor's key
|
||||
algo := httpsig.Algorithm(setting.Federation.Algorithms[0])
|
||||
authenticated = v.Verify(pubKey, algo) == nil
|
||||
return authenticated, err
|
||||
}
|
||||
|
||||
// ReqHTTPSignature function
|
||||
func ReqHTTPSignature() func(ctx *gitea_context.APIContext) {
|
||||
return func(ctx *gitea_context.APIContext) {
|
||||
if authenticated, err := verifyHTTPSignatures(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
} else if !authenticated {
|
||||
ctx.APIError(http.StatusForbidden, "request signature verification failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-12
@@ -936,18 +936,8 @@ func Routes() *web.Router {
|
||||
}
|
||||
|
||||
if setting.Federation.Enabled {
|
||||
m.Get("/nodeinfo", misc.NodeInfo)
|
||||
m.Group("/activitypub", func() {
|
||||
// deprecated, remove in 1.20, use /user-id/{user-id} instead
|
||||
m.Group("/user/{username}", func() {
|
||||
m.Get("", activitypub.Person)
|
||||
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||
m.Group("/user-id/{user-id}", func() {
|
||||
m.Get("", activitypub.Person)
|
||||
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
|
||||
}, context.UserIDAssignmentAPI(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
|
||||
m.Get("/nodeinfo", activitypub.NotImplemented)
|
||||
m.Any("/activitypub/*", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub), activitypub.NotImplemented)
|
||||
}
|
||||
|
||||
// Misc (public accessible)
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package misc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
const cacheKeyNodeInfoUsage = "API_NodeInfoUsage"
|
||||
|
||||
// NodeInfo returns the NodeInfo for the Gitea instance to allow for federation
|
||||
func NodeInfo(ctx *context.APIContext) {
|
||||
// swagger:operation GET /nodeinfo miscellaneous getNodeInfo
|
||||
// ---
|
||||
// summary: Returns the nodeinfo of the Gitea application
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/NodeInfo"
|
||||
|
||||
nodeInfoUsage := structs.NodeInfoUsage{}
|
||||
if setting.Federation.ShareUserStatistics {
|
||||
cached, _ := ctx.Cache.GetJSON(cacheKeyNodeInfoUsage, &nodeInfoUsage)
|
||||
if !cached {
|
||||
usersTotal := int(user_model.CountUsers(ctx, nil))
|
||||
now := time.Now()
|
||||
timeOneMonthAgo := now.AddDate(0, -1, 0).Unix()
|
||||
timeHaveYearAgo := now.AddDate(0, -6, 0).Unix()
|
||||
usersActiveMonth := int(user_model.CountUsers(ctx, &user_model.CountUserFilter{LastLoginSince: &timeOneMonthAgo}))
|
||||
usersActiveHalfyear := int(user_model.CountUsers(ctx, &user_model.CountUserFilter{LastLoginSince: &timeHaveYearAgo}))
|
||||
|
||||
allIssues, _ := issues_model.CountIssues(ctx, &issues_model.IssuesOptions{})
|
||||
allComments, _ := issues_model.CountComments(ctx, &issues_model.FindCommentsOptions{})
|
||||
|
||||
nodeInfoUsage = structs.NodeInfoUsage{
|
||||
Users: structs.NodeInfoUsageUsers{
|
||||
Total: usersTotal,
|
||||
ActiveMonth: usersActiveMonth,
|
||||
ActiveHalfyear: usersActiveHalfyear,
|
||||
},
|
||||
LocalPosts: int(allIssues),
|
||||
LocalComments: int(allComments),
|
||||
}
|
||||
|
||||
if err := ctx.Cache.PutJSON(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodeInfo := &structs.NodeInfo{
|
||||
Version: "2.1",
|
||||
Software: structs.NodeInfoSoftware{
|
||||
Name: "gitea",
|
||||
Version: setting.AppVer,
|
||||
Repository: "https://github.com/go-gitea/gitea.git",
|
||||
Homepage: "https://gitea.io/",
|
||||
},
|
||||
Protocols: []string{"activitypub"},
|
||||
Services: structs.NodeInfoServices{
|
||||
Inbound: []string{},
|
||||
Outbound: []string{"rss2.0"},
|
||||
},
|
||||
OpenRegistrations: setting.Service.ShowRegistrationButton,
|
||||
Usage: nodeInfoUsage,
|
||||
}
|
||||
ctx.JSON(http.StatusOK, nodeInfo)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package swagger
|
||||
|
||||
import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
// ActivityPub
|
||||
// swagger:response ActivityPub
|
||||
type swaggerResponseActivityPub struct {
|
||||
// in:body
|
||||
Body api.ActivityPub `json:"body"`
|
||||
}
|
||||
Reference in New Issue
Block a user