2022-11-27 00:34:24 +00:00
|
|
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
2022-12-31 17:58:01 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2022-11-27 00:34:24 +00:00
|
|
|
|
|
|
|
package activitypub
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-01-12 21:28:35 +00:00
|
|
|
"encoding/json"
|
2022-11-27 00:34:24 +00:00
|
|
|
"errors"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"code.gitea.io/gitea/models/auth"
|
2022-11-27 01:40:15 +00:00
|
|
|
issues_model "code.gitea.io/gitea/models/issues"
|
2022-11-27 00:34:24 +00:00
|
|
|
repo_model "code.gitea.io/gitea/models/repo"
|
|
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
|
|
"code.gitea.io/gitea/modules/forgefed"
|
|
|
|
repo_module "code.gitea.io/gitea/modules/repository"
|
|
|
|
"code.gitea.io/gitea/modules/setting"
|
|
|
|
"code.gitea.io/gitea/services/activitypub"
|
|
|
|
issue_service "code.gitea.io/gitea/services/issue"
|
2022-11-27 01:40:15 +00:00
|
|
|
pull_service "code.gitea.io/gitea/services/pull"
|
2022-11-27 00:34:24 +00:00
|
|
|
repo_service "code.gitea.io/gitea/services/repository"
|
|
|
|
user_service "code.gitea.io/gitea/services/user"
|
|
|
|
|
|
|
|
ap "github.com/go-ap/activitypub"
|
|
|
|
)
|
|
|
|
|
2023-01-12 21:28:35 +00:00
|
|
|
|
2022-11-27 00:34:24 +00:00
|
|
|
// Create a new federated user from a Person object
|
|
|
|
func createPerson(ctx context.Context, person *ap.Person) error {
|
2023-01-12 21:28:35 +00:00
|
|
|
_, err := user_model.GetUserByIRI(ctx, person.GetLink().String())
|
2023-01-12 21:35:11 +00:00
|
|
|
if !user_model.IsErrUserNotExist(err) {
|
2023-01-12 21:28:35 +00:00
|
|
|
// User already exists
|
2022-11-27 00:34:24 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-01-12 21:28:35 +00:00
|
|
|
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())
|
2022-11-27 00:34:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-01-12 21:28:35 +00:00
|
|
|
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")
|
2022-11-27 00:34:24 +00:00
|
|
|
}
|
2023-01-12 21:28:35 +00:00
|
|
|
name := subjectSplit[1]
|
2022-11-27 00:34:24 +00:00
|
|
|
|
|
|
|
var email string
|
|
|
|
if person.Location != nil {
|
|
|
|
email = person.Location.GetLink().String()
|
|
|
|
} else {
|
|
|
|
// This might not even work
|
|
|
|
email = strings.ReplaceAll(name, "@", "+") + "@" + setting.Service.NoReplyAddress
|
|
|
|
}
|
|
|
|
|
|
|
|
if person.PublicKey.PublicKeyPem == "" {
|
|
|
|
return errors.New("person public key not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
user := &user_model.User{
|
2022-11-27 22:37:28 +00:00
|
|
|
Name: name,
|
|
|
|
Email: email,
|
|
|
|
LoginType: auth.Federated,
|
|
|
|
LoginName: person.GetLink().String(),
|
|
|
|
EmailNotificationsPreference: user_model.EmailNotificationsDisabled,
|
2022-11-27 00:34:24 +00:00
|
|
|
}
|
|
|
|
err = user_model.CreateUser(user)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-01-12 21:28:35 +00:00
|
|
|
if person.Name != nil {
|
|
|
|
user.FullName = person.Name.String()
|
|
|
|
}
|
2022-11-27 00:34:24 +00:00
|
|
|
if person.Icon != nil {
|
2023-01-12 21:28:35 +00:00
|
|
|
// Fetch and save user icon
|
2022-11-27 04:18:39 +00:00
|
|
|
icon, err := ap.ToObject(person.Icon)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-27 00:34:24 +00:00
|
|
|
iconURL, err := icon.URL.GetLink().URL()
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = user_model.SetUserSetting(user.ID, user_model.UserActivityPubPrivPem, "")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-01-12 21:28:35 +00:00
|
|
|
// Set public key
|
2022-11-27 00:34:24 +00:00
|
|
|
return user_model.SetUserSetting(user.ID, user_model.UserActivityPubPubPem, person.PublicKey.PublicKeyPem)
|
|
|
|
}
|
|
|
|
|
2022-11-27 02:05:36 +00:00
|
|
|
// Create a new federated repo from a Repository object
|
|
|
|
func createRepository(ctx context.Context, repository *forgefed.Repository) error {
|
2023-01-12 21:28:35 +00:00
|
|
|
user, err := user_model.GetUserByIRI(ctx, repository.AttributedTo.GetLink().String())
|
2022-11-27 00:34:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-11-27 01:40:15 +00:00
|
|
|
// Check if repo exists
|
2022-12-22 21:12:50 +00:00
|
|
|
_, err = repo_model.GetRepositoryByOwnerAndName(ctx, user.Name, repository.Name.String())
|
2022-11-27 00:34:24 +00:00
|
|
|
if err == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
repo, err := repo_service.CreateRepository(user, user, repo_module.CreateRepoOptions{
|
2022-11-27 04:18:39 +00:00
|
|
|
Name: repository.Name.String(),
|
|
|
|
OriginalURL: repository.GetLink().String(),
|
2022-11-27 00:34:24 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if repository.ForkedFrom != nil {
|
|
|
|
repo.IsFork = true
|
|
|
|
forkedFrom, err := activitypub.RepositoryIRIToRepository(ctx, repository.ForkedFrom.GetLink())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
repo.ForkID = forkedFrom.ID
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-27 02:05:36 +00:00
|
|
|
func createRepositoryFromIRI(ctx context.Context, repoIRI ap.IRI) error {
|
|
|
|
repoURL, err := url.Parse(repoIRI.String())
|
2022-11-27 01:40:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Fetch repository object
|
|
|
|
resp, err := activitypub.Fetch(repoURL)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-27 02:05:36 +00:00
|
|
|
|
2022-11-27 01:40:15 +00:00
|
|
|
// Parse repository object
|
|
|
|
ap.ItemTyperFunc = forgefed.GetItemByType
|
|
|
|
ap.JSONItemUnmarshal = forgefed.JSONUnmarshalerFn
|
|
|
|
ap.NotEmptyChecker = forgefed.NotEmpty
|
|
|
|
object, err := ap.UnmarshalJSON(resp)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-27 02:05:36 +00:00
|
|
|
|
2022-11-27 01:40:15 +00:00
|
|
|
// Create federated repo
|
2022-11-27 02:05:36 +00:00
|
|
|
return forgefed.OnRepository(object, func(r *forgefed.Repository) error {
|
2022-11-27 01:40:15 +00:00
|
|
|
return createRepository(ctx, r)
|
|
|
|
})
|
2022-11-27 02:05:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create a ticket
|
|
|
|
func createTicket(ctx context.Context, ticket *forgefed.Ticket) error {
|
|
|
|
if ticket.Origin != nil && ticket.Target != nil {
|
|
|
|
return createPullRequest(ctx, ticket)
|
|
|
|
}
|
|
|
|
return createIssue(ctx, ticket)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create an issue
|
|
|
|
func createIssue(ctx context.Context, ticket *forgefed.Ticket) error {
|
|
|
|
err := createRepositoryFromIRI(ctx, ticket.Context.GetLink())
|
2022-11-27 01:40:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-11-27 00:34:24 +00:00
|
|
|
// Construct issue
|
2023-01-12 21:28:35 +00:00
|
|
|
user, err := user_model.GetUserByIRI(ctx, ticket.AttributedTo.GetLink().String())
|
2022-11-27 00:34:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
repo, err := activitypub.RepositoryIRIToRepository(ctx, ap.IRI(ticket.Context.GetLink().String()))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
idx, err := strconv.ParseInt(ticket.Name.String()[1:], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-27 01:40:15 +00:00
|
|
|
issue := &issues_model.Issue{
|
2023-01-01 18:46:42 +00:00
|
|
|
Index: idx, // TODO: This doesn't seem to work?
|
2022-11-27 18:38:20 +00:00
|
|
|
RepoID: repo.ID,
|
|
|
|
Repo: repo,
|
|
|
|
Title: ticket.Summary.String(),
|
|
|
|
PosterID: user.ID,
|
|
|
|
Poster: user,
|
|
|
|
Content: ticket.Content.String(),
|
|
|
|
OriginalAuthor: ticket.GetLink().String(), // Create new database field to store IRI?
|
|
|
|
IsClosed: ticket.IsResolved,
|
2022-11-27 00:34:24 +00:00
|
|
|
}
|
|
|
|
return issue_service.NewIssue(repo, issue, nil, nil, nil)
|
|
|
|
}
|
|
|
|
|
2022-11-27 01:40:15 +00:00
|
|
|
// Create a pull request
|
|
|
|
func createPullRequest(ctx context.Context, ticket *forgefed.Ticket) error {
|
2022-11-27 02:05:36 +00:00
|
|
|
err := createRepositoryFromIRI(ctx, ticket.Context.GetLink())
|
2022-11-27 01:40:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-01-12 21:28:35 +00:00
|
|
|
user, err := user_model.GetUserByIRI(ctx, ticket.AttributedTo.GetLink().String())
|
2022-11-27 02:05:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-27 01:40:15 +00:00
|
|
|
|
2022-11-27 02:05:36 +00:00
|
|
|
// Extract origin and target repos
|
|
|
|
originUsername, originReponame, originBranch, err := activitypub.BranchIRIToName(ticket.Origin.GetLink())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-12-22 21:12:50 +00:00
|
|
|
originRepo, err := repo_model.GetRepositoryByOwnerAndName(ctx, originUsername, originReponame)
|
2022-11-27 02:05:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
targetUsername, targetReponame, targetBranch, err := activitypub.BranchIRIToName(ticket.Target.GetLink())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-12-22 21:12:50 +00:00
|
|
|
targetRepo, err := repo_model.GetRepositoryByOwnerAndName(ctx, targetUsername, targetReponame)
|
2022-11-27 02:05:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-27 01:40:15 +00:00
|
|
|
|
2022-11-27 02:05:36 +00:00
|
|
|
idx, err := strconv.ParseInt(ticket.Name.String()[1:], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-27 01:40:15 +00:00
|
|
|
prIssue := &issues_model.Issue{
|
2022-11-27 02:05:36 +00:00
|
|
|
Index: idx,
|
2022-11-27 01:40:15 +00:00
|
|
|
RepoID: targetRepo.ID,
|
2022-11-27 02:05:36 +00:00
|
|
|
Title: ticket.Summary.String(),
|
|
|
|
PosterID: user.ID,
|
|
|
|
Poster: user,
|
2022-11-27 01:40:15 +00:00
|
|
|
IsPull: true,
|
2022-11-27 02:05:36 +00:00
|
|
|
Content: ticket.Content.String(),
|
|
|
|
IsClosed: ticket.IsResolved,
|
2022-11-27 01:40:15 +00:00
|
|
|
}
|
|
|
|
pr := &issues_model.PullRequest{
|
|
|
|
HeadRepoID: originRepo.ID,
|
|
|
|
BaseRepoID: targetRepo.ID,
|
|
|
|
HeadBranch: originBranch,
|
|
|
|
BaseBranch: targetBranch,
|
|
|
|
HeadRepo: originRepo,
|
|
|
|
BaseRepo: targetRepo,
|
|
|
|
MergeBase: "",
|
|
|
|
Type: issues_model.PullRequestGitea,
|
|
|
|
}
|
|
|
|
return pull_service.NewPullRequest(ctx, targetRepo, prIssue, []int64{}, []string{}, pr, []int64{})
|
|
|
|
}
|
|
|
|
|
2022-11-27 00:34:24 +00:00
|
|
|
// Create a comment
|
|
|
|
func createComment(ctx context.Context, note *ap.Note) error {
|
2023-01-12 21:28:35 +00:00
|
|
|
user, err := user_model.GetUserByIRI(ctx, note.AttributedTo.GetLink().String())
|
2022-11-27 00:34:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
username, reponame, idx, err := activitypub.TicketIRIToName(note.Context.GetLink())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-12-22 21:12:50 +00:00
|
|
|
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, username, reponame)
|
2022-11-27 00:34:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-27 01:40:15 +00:00
|
|
|
issue, err := issues_model.GetIssueByIndex(repo.ID, idx)
|
2022-11-27 00:34:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-12-22 21:12:50 +00:00
|
|
|
_, err = issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
|
2022-11-27 22:30:00 +00:00
|
|
|
Doer: user,
|
|
|
|
Repo: repo,
|
|
|
|
Issue: issue,
|
|
|
|
OldTitle: note.GetLink().String(),
|
|
|
|
Content: note.Content.String(),
|
2022-11-27 00:34:24 +00:00
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|