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

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

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

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

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

Loading…
Cancel
Save