Make sure API responses always refer to username in original case

Copied from what I wrote on #19133 discussion: Handling username case is a very tricky issue and I've already encountered a Mastodon <-> Gitea federation bug due to Gitea considering Ta180m and ta180m to be the same user while Mastodon thinks they are two different users. I think the best way forward is for Gitea to only use the original case version of the username for federation so other AP software don't get confused.
This commit is contained in:
Anthony Wang 2022-06-14 12:01:41 -05:00
parent add8469813
commit e60158c70b
No known key found for this signature in database
GPG key ID: BC96B00AEC5F2D76
5 changed files with 16 additions and 23 deletions

View file

@ -52,7 +52,7 @@ func TestWebfinger(t *testing.T) {
var jrd webfingerJRD
DecodeJSON(t, resp, &jrd)
assert.Equal(t, "acct:user2@"+appURL.Host, jrd.Subject)
assert.ElementsMatch(t, []string{user.HTMLURL(), appURL.String() + "api/v1/activitypub/user/" + user.Name}, jrd.Aliases)
assert.ElementsMatch(t, []string{user.HTMLURL(), appURL.String() + "api/v1/activitypub/user/" + url.PathEscape(user.Name)}, jrd.Aliases)
req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=acct:%s@%s", user.LowerName, "unknown.host"))
MakeRequest(t, req, http.StatusBadRequest)

View file

@ -6,14 +6,12 @@ package activitypub
import (
"net/http"
"strings"
"code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/api/v1/user"
ap "github.com/go-ap/activitypub"
)
@ -35,35 +33,29 @@ func Person(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/ActivityPub"
user := user.GetUserByParamsName(ctx, "username")
if user == nil {
return
}
username := ctx.Params("username")
link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/")
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name
person := ap.PersonNew(ap.IRI(link))
person.Name = ap.NaturalLanguageValuesNew()
err := person.Name.Set("en", ap.Content(user.FullName))
err := person.Name.Set("en", ap.Content(ctx.ContextUser.FullName))
if err != nil {
ctx.Error(http.StatusInternalServerError, "Set Name", err)
return
}
person.PreferredUsername = ap.NaturalLanguageValuesNew()
err = person.PreferredUsername.Set("en", ap.Content(username))
err = person.PreferredUsername.Set("en", ap.Content(ctx.ContextUser.Name))
if err != nil {
ctx.Error(http.StatusInternalServerError, "Set PreferredUsername", err)
return
}
person.URL = ap.IRI(setting.AppURL + username)
person.URL = ap.IRI(ctx.ContextUser.HTMLURL())
person.Icon = ap.Image{
Type: ap.ImageType,
MediaType: "image/png",
URL: ap.IRI(user.AvatarLink()),
URL: ap.IRI(ctx.ContextUser.AvatarLink()),
}
person.Inbox = nil
@ -74,7 +66,7 @@ func Person(ctx *context.APIContext) {
person.PublicKey.ID = ap.IRI(link + "#main-key")
person.PublicKey.Owner = ap.IRI(link)
publicKeyPem, err := activitypub.GetPublicKey(user)
publicKeyPem, err := activitypub.GetPublicKey(ctx.ContextUser)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetPublicKey", err)
return
@ -84,12 +76,14 @@ func Person(ctx *context.APIContext) {
binary, err := person.MarshalJSON()
if err != nil {
ctx.Error(http.StatusInternalServerError, "Serialize", err)
return
}
var jsonmap map[string]interface{}
err = json.Unmarshal(binary, &jsonmap)
if err != nil {
ctx.Error(http.StatusInternalServerError, "Unmarshall", err)
ctx.Error(http.StatusInternalServerError, "Unmarshal", err)
return
}
jsonmap["@context"] = []string{"https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"}

View file

@ -5,7 +5,6 @@
package activitypub
import (
"context"
"crypto"
"crypto/x509"
"encoding/pem"
@ -25,7 +24,7 @@ import (
"github.com/go-fed/httpsig"
)
func getPublicKeyFromResponse(ctx context.Context, b []byte, keyID *url.URL) (p crypto.PublicKey, err error) {
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 {
@ -34,7 +33,7 @@ func getPublicKeyFromResponse(ctx context.Context, b []byte, keyID *url.URL) (p
}
pubKey := person.PublicKey
if pubKey.ID.String() != keyID.String() {
err = fmt.Errorf("cannot find publicKey with id: %s in %s", keyID, b)
err = fmt.Errorf("cannot find publicKey with id: %s in %s", keyID, string(b))
return
}
pubKeyPem := pubKey.PublicKeyPem
@ -84,7 +83,7 @@ func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, er
if err != nil {
return
}
pubKey, err := getPublicKeyFromResponse(*ctx, b, idIRI)
pubKey, err := getPublicKeyFromResponse(b, idIRI)
if err != nil {
return
}

View file

@ -648,7 +648,7 @@ func Routes() *web.Route {
m.Group("/user/{username}", func() {
m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqSignature(), activitypub.PersonInbox)
})
}, context_service.UserAssignmentAPI())
})
}
m.Get("/signing-key.gpg", misc.SigningKey)

View file

@ -87,7 +87,7 @@ func WebfingerQuery(ctx *context.Context) {
aliases := []string{
u.HTMLURL(),
appURL.String() + "api/v1/activitypub/user/" + strings.ToLower(u.Name),
appURL.String() + "api/v1/activitypub/user/" + url.PathEscape(u.Name),
}
if !u.KeepEmailPrivate {
aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email))
@ -106,7 +106,7 @@ func WebfingerQuery(ctx *context.Context) {
{
Rel: "self",
Type: "application/activity+json",
Href: appURL.String() + "api/v1/activitypub/user/" + strings.ToLower(u.Name),
Href: appURL.String() + "api/v1/activitypub/user/" + url.PathEscape(u.Name),
},
{
Rel: "http://ostatus.org/schema/1.0/subscribe",