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.
pull/19981/head
Anthony Wang 3 years ago
parent add8469813
commit e60158c70b
No known key found for this signature in database
GPG Key ID: BC96B00AEC5F2D76

@ -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)

@ -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"}

@ -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
}

@ -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)

@ -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",

Loading…
Cancel
Save