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
Signed by: a
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 var jrd webfingerJRD
DecodeJSON(t, resp, &jrd) DecodeJSON(t, resp, &jrd)
assert.Equal(t, "acct:user2@"+appURL.Host, jrd.Subject) 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")) req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=acct:%s@%s", user.LowerName, "unknown.host"))
MakeRequest(t, req, http.StatusBadRequest) MakeRequest(t, req, http.StatusBadRequest)

View file

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

View file

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

View file

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

View file

@ -87,7 +87,7 @@ func WebfingerQuery(ctx *context.Context) {
aliases := []string{ aliases := []string{
u.HTMLURL(), 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 { if !u.KeepEmailPrivate {
aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email)) aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email))
@ -106,7 +106,7 @@ func WebfingerQuery(ctx *context.Context) {
{ {
Rel: "self", Rel: "self",
Type: "application/activity+json", 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", Rel: "http://ostatus.org/schema/1.0/subscribe",