Replace personIRIToUser with GetUserByIRI, do a WebFinger request to get correct username

This commit is contained in:
Anthony Wang 2023-01-12 21:28:35 +00:00
parent 3aba06d429
commit bb38b9737f
No known key found for this signature in database
GPG Key ID: 42A5B952E6DD8D38
10 changed files with 101 additions and 110 deletions

View File

@ -9,6 +9,7 @@ import (
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"errors"
"fmt"
"net/url"
"os"
@ -1028,6 +1029,29 @@ func GetUserByName(ctx context.Context, name string) (*User, error) {
return u, nil
}
// GetUserByIRI returns user by given IRI.
func GetUserByIRI(ctx context.Context, iri string) (*User, error) {
if len(iri) == 0 {
return nil, ErrUserNotExist{0, iri, 0}
}
iriSplit := strings.Split(iri, "/")
if len(iriSplit) < 4 {
return nil, errors.New("not a Person actor IRI")
}
if iriSplit[2] == setting.Domain {
// Local user
return GetUserByName(ctx, iriSplit[len(iriSplit)-1])
}
u := &User{LoginName: iri}
has, err := db.GetEngine(ctx).Get(u)
if err != nil {
return nil, err
} else if !has {
return nil, ErrUserNotExist{0, iri, 0}
}
return u, nil
}
// GetUserEmailsByNames returns a list of e-mails corresponds to names of users
// that have their email notifications set to enabled or onmention.
func GetUserEmailsByNames(ctx context.Context, names []string) []string {

View File

@ -8,6 +8,7 @@ import (
"net/url"
"strconv"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/forgefed"
"code.gitea.io/gitea/modules/setting"
@ -51,12 +52,12 @@ func AuthorizeInteraction(ctx *context.Context) {
ctx.ServerError("FederatedUserNew", err)
return
}
name, err := activitypub.PersonIRIToName(object.GetLink())
user, err := user_model.GetUserByIRI(ctx, object.GetLink().String())
if err != nil {
ctx.ServerError("PersonIRIToName", err)
ctx.ServerError("GetUserByIRI", err)
return
}
ctx.Redirect(setting.AppURL + name)
ctx.Redirect(setting.AppURL + user.Name)
case forgefed.RepositoryType:
// Federated repository
err = forgefed.OnRepository(object, func(r *forgefed.Repository) error {

View File

@ -5,6 +5,7 @@ package activitypub
import (
"context"
"encoding/json"
"errors"
"net/url"
"strconv"
@ -26,20 +27,46 @@ import (
ap "github.com/go-ap/activitypub"
)
// Create a new federated user from a Person object
func createPerson(ctx context.Context, person *ap.Person) error {
name, err := activitypub.PersonIRIToName(person.GetLink())
if err != nil {
_, err := user_model.GetUserByIRI(ctx, person.GetLink().String())
if user_model.IsErrUserNotExist(err) {
// User already exists
return err
}
exists, err := user_model.IsUserExist(ctx, 0, name)
personIRISplit := strings.Split(person.GetLink().String(), "/")
if len(personIRISplit) < 4 {
return errors.New("not a Person actor IRI")
}
// Get instance by taking the domain of the IRI
instance := personIRISplit[2]
if instance == setting.Domain {
// Local user
return nil
}
// Send a WebFinger request to get the username
uri, err := url.Parse("https://" + instance + "/.well-known/webfinger?resource=" + person.GetLink().String())
if err != nil {
return err
}
if exists {
return nil
resp, err := activitypub.Fetch(uri)
if err != nil {
return err
}
var data activitypub.WebfingerJRD
err = json.Unmarshal(resp, &data)
if err != nil {
return err
}
subjectSplit := strings.Split(data.Subject, ":")
if subjectSplit[0] != "acct" {
return errors.New("subject is not an acct URI")
}
name := subjectSplit[1]
var email string
if person.Location != nil {
@ -55,7 +82,6 @@ func createPerson(ctx context.Context, person *ap.Person) error {
user := &user_model.User{
Name: name,
FullName: person.Name.String(), // May not exist!!
Email: email,
LoginType: auth.Federated,
LoginName: person.GetLink().String(),
@ -66,7 +92,11 @@ func createPerson(ctx context.Context, person *ap.Person) error {
return err
}
if person.Name != nil {
user.FullName = person.Name.String()
}
if person.Icon != nil {
// Fetch and save user icon
icon, err := ap.ToObject(person.Icon)
if err != nil {
return err
@ -75,12 +105,10 @@ func createPerson(ctx context.Context, person *ap.Person) error {
if err != nil {
return err
}
body, err := activitypub.Fetch(iconURL)
if err != nil {
return err
}
err = user_service.UploadAvatar(user, body)
if err != nil {
return err
@ -91,44 +119,13 @@ func createPerson(ctx context.Context, person *ap.Person) error {
if err != nil {
return err
}
// Set public key
return user_model.SetUserSetting(user.ID, user_model.UserActivityPubPubPem, person.PublicKey.PublicKeyPem)
}
func createPersonFromIRI(ctx context.Context, personIRI ap.IRI) error {
ownerURL, err := url.Parse(personIRI.String())
if err != nil {
return err
}
// Fetch person object
resp, err := activitypub.Fetch(ownerURL)
if err != nil {
return err
}
// Parse person object
ap.ItemTyperFunc = forgefed.GetItemByType
ap.JSONItemUnmarshal = forgefed.JSONUnmarshalerFn
ap.NotEmptyChecker = forgefed.NotEmpty
object, err := ap.UnmarshalJSON(resp)
if err != nil {
return err
}
// Create federated user
person, err := ap.ToActor(object)
if err != nil {
return err
}
return createPerson(ctx, person)
}
// Create a new federated repo from a Repository object
func createRepository(ctx context.Context, repository *forgefed.Repository) error {
err := createPersonFromIRI(ctx, repository.AttributedTo.GetLink())
if err != nil {
return err
}
user, err := activitypub.PersonIRIToUser(ctx, repository.AttributedTo.GetLink())
user, err := user_model.GetUserByIRI(ctx, repository.AttributedTo.GetLink().String())
if err != nil {
return err
}
@ -200,7 +197,7 @@ func createIssue(ctx context.Context, ticket *forgefed.Ticket) error {
}
// Construct issue
user, err := activitypub.PersonIRIToUser(ctx, ap.IRI(ticket.AttributedTo.GetLink().String()))
user, err := user_model.GetUserByIRI(ctx, ticket.AttributedTo.GetLink().String())
if err != nil {
return err
}
@ -233,7 +230,7 @@ func createPullRequest(ctx context.Context, ticket *forgefed.Ticket) error {
return err
}
user, err := activitypub.PersonIRIToUser(ctx, ticket.AttributedTo.GetLink())
user, err := user_model.GetUserByIRI(ctx, ticket.AttributedTo.GetLink().String())
if err != nil {
return err
}
@ -285,12 +282,7 @@ func createPullRequest(ctx context.Context, ticket *forgefed.Ticket) error {
// Create a comment
func createComment(ctx context.Context, note *ap.Note) error {
err := createPersonFromIRI(ctx, note.AttributedTo.GetLink())
if err != nil {
return err
}
user, err := activitypub.PersonIRIToUser(ctx, note.AttributedTo.GetLink())
user, err := user_model.GetUserByIRI(ctx, note.AttributedTo.GetLink().String())
if err != nil {
return err
}

View File

@ -6,8 +6,8 @@ package activitypub
import (
"context"
user_model "code.gitea.io/gitea/models/user"
user_service "code.gitea.io/gitea/services/user"
"code.gitea.io/gitea/services/activitypub"
ap "github.com/go-ap/activitypub"
)
@ -20,9 +20,9 @@ func delete(ctx context.Context, delete ap.Delete) error {
if actorIRI != objectIRI {
return nil
}
// Object is the user getting deleted
objectUser, err := activitypub.PersonIRIToUser(ctx, objectIRI)
objectUser, err := user_model.GetUserByIRI(ctx, objectIRI.String())
if err != nil {
return err
}

View File

@ -18,14 +18,14 @@ import (
func follow(ctx context.Context, follow ap.Follow) error {
// Actor is the user performing the follow
actorIRI := follow.Actor.GetLink()
actorUser, err := activitypub.PersonIRIToUser(ctx, actorIRI)
actorUser, err := user_model.GetUserByIRI(ctx, actorIRI.String())
if err != nil {
return err
}
// Object is the user being followed
objectIRI := follow.Object.GetLink()
objectUser, err := activitypub.PersonIRIToUser(ctx, objectIRI)
objectUser, err := user_model.GetUserByIRI(ctx, objectIRI.String())
// Must be a local user
if err != nil || strings.Contains(objectUser.Name, "@") {
return err
@ -54,14 +54,14 @@ func unfollow(ctx context.Context, unfollow ap.Undo) error {
// Actor is the user performing the undo follow
actorIRI := follow.Actor.GetLink()
actorUser, err := activitypub.PersonIRIToUser(ctx, actorIRI)
actorUser, err := user_model.GetUserByIRI(ctx, actorIRI.String())
if err != nil {
return err
}
// Object is the user being unfollowed
objectIRI := follow.Object.GetLink()
objectUser, err := activitypub.PersonIRIToUser(ctx, objectIRI)
objectUser, err := user_model.GetUserByIRI(ctx, objectIRI.String())
// Must be a local user
if err != nil || strings.Contains(objectUser.Name, "@") {
return err

View File

@ -78,8 +78,6 @@ func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, er
return
}
// 4. Create a federated user for the actor
// TODO: This is a very bad place for creating federated users
// We end up creating way more users than necessary!
var person ap.Person
err = person.UnmarshalJSON(b)
if err != nil {

View File

@ -9,6 +9,7 @@ import (
"strings"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/services/activitypub"
ap "github.com/go-ap/activitypub"
@ -16,7 +17,7 @@ import (
// Process a Like activity to star a repository
func star(ctx context.Context, like ap.Like) (err error) {
user, err := activitypub.PersonIRIToUser(ctx, like.Actor.GetLink())
user, err := user_model.GetUserByIRI(ctx, like.Actor.GetLink().String())
if err != nil {
return
}
@ -33,7 +34,7 @@ func unstar(ctx context.Context, unlike ap.Undo) (err error) {
if !ok {
return errors.New("could not cast object to like")
}
user, err := activitypub.PersonIRIToUser(ctx, like.Actor.GetLink())
user, err := user_model.GetUserByIRI(ctx, like.Actor.GetLink().String())
if err != nil {
return
}

View File

@ -13,26 +13,11 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/activitypub"
)
// https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-webfinger-14#section-4.4
type webfingerJRD struct {
Subject string `json:"subject,omitempty"`
Aliases []string `json:"aliases,omitempty"`
Properties map[string]interface{} `json:"properties,omitempty"`
Links []*webfingerLink `json:"links,omitempty"`
}
type webfingerLink struct {
Rel string `json:"rel,omitempty"`
Type string `json:"type,omitempty"`
Href string `json:"href,omitempty"`
Template string `json:"template,omitempty"`
Titles map[string]string `json:"titles,omitempty"`
Properties map[string]interface{} `json:"properties,omitempty"`
}
// WebfingerQuery returns information about a resource
// https://datatracker.ietf.org/doc/html/rfc7565
func WebfingerQuery(ctx *context.Context) {
@ -65,6 +50,8 @@ func WebfingerQuery(ctx *context.Context) {
if u != nil && u.KeepEmailPrivate {
err = user_model.ErrUserNotExist{}
}
case "https":
u, err = user_model.GetUserByIRI(ctx, ctx.FormString("resource"))
default:
ctx.Error(http.StatusBadRequest)
return
@ -92,7 +79,7 @@ func WebfingerQuery(ctx *context.Context) {
aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email))
}
links := []*webfingerLink{
links := []*activitypub.WebfingerLink{
{
Rel: "http://webfinger.net/rel/profile-page",
Type: "text/html",
@ -114,7 +101,7 @@ func WebfingerQuery(ctx *context.Context) {
}
ctx.Resp.Header().Add("Access-Control-Allow-Origin", "*")
ctx.JSON(http.StatusOK, &webfingerJRD{
ctx.JSON(http.StatusOK, &activitypub.WebfingerJRD{
Subject: fmt.Sprintf("acct:%s@%s", url.QueryEscape(u.Name), appURL.Host),
Aliases: aliases,
Links: links,

View File

@ -9,40 +9,11 @@ import (
"strings"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
ap "github.com/go-ap/activitypub"
)
// Returns the username corresponding to a Person actor IRI
func PersonIRIToName(personIRI ap.IRI) (string, error) {
personIRISplit := strings.Split(personIRI.String(), "/")
if len(personIRISplit) < 4 {
return "", errors.New("not a Person actor IRI")
}
instance := personIRISplit[2]
name := personIRISplit[len(personIRISplit)-1]
if instance == setting.Domain {
// Local user
return name, nil
}
// Remote user
// Get name in username@instance.com format
return name + "@" + instance, nil
}
// Returns the user corresponding to a Person actor IRI
func PersonIRIToUser(ctx context.Context, personIRI ap.IRI) (*user_model.User, error) {
name, err := PersonIRIToName(personIRI)
if err != nil {
return nil, err
}
return user_model.GetUserByName(ctx, name)
}
// Returns the owner and name corresponding to a Repository actor IRI
func RepositoryIRIToName(repoIRI ap.IRI) (string, string, error) {
repoIRISplit := strings.Split(repoIRI.String(), "/")

View File

@ -0,0 +1,17 @@
package activitypub;
type WebfingerJRD struct {
Subject string `json:"subject,omitempty"`
Aliases []string `json:"aliases,omitempty"`
Properties map[string]interface{} `json:"properties,omitempty"`
Links []*WebfingerLink `json:"links,omitempty"`
}
type WebfingerLink struct {
Rel string `json:"rel,omitempty"`
Type string `json:"type,omitempty"`
Href string `json:"href,omitempty"`
Template string `json:"template,omitempty"`
Titles map[string]string `json:"titles,omitempty"`
Properties map[string]interface{} `json:"properties,omitempty"`
}