From 1066cfe785875c25ea897b5454491ef3c490c82c Mon Sep 17 00:00:00 2001 From: Anthony Wang Date: Sun, 27 Nov 2022 04:18:39 +0000 Subject: [PATCH] Implement commenting and fix lint errors --- models/issues/issue.go | 9 ++++ models/repo/repo.go | 7 +++ models/user/user.go | 7 +++ modules/forgefed/forgefed.go | 50 ++++++++++--------- .../v1/activitypub/authorize_interaction.go | 7 ++- routers/api/v1/activitypub/create.go | 18 +++++-- routers/api/v1/activitypub/person.go | 42 ++++++++-------- routers/api/v1/activitypub/repo.go | 12 ++--- routers/api/v1/activitypub/ticket.go | 12 ++--- .../activitypub/{follow.go => activities.go} | 28 ++--------- services/activitypub/objects.go | 22 ++++++++ services/comments/comments.go | 19 +++++++ services/user/user.go | 16 ++++-- 13 files changed, 158 insertions(+), 91 deletions(-) rename services/activitypub/{follow.go => activities.go} (67%) create mode 100644 services/activitypub/objects.go diff --git a/models/issues/issue.go b/models/issues/issue.go index ca48f425f2..decfdf697e 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -27,6 +27,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/references" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -2471,3 +2472,11 @@ func DeleteOrphanedIssues() error { } return nil } + +func (issue *Issue) GetIRI() string { + _ = issue.LoadRepo(db.DefaultContext) + if strings.Contains(issue.Repo.OwnerName, "@") { + return issue.OriginalAuthor + } + return setting.AppURL + "api/v1/activitypub/ticket/" + issue.Repo.OwnerName + "/" + issue.Repo.Name + "/" + strconv.FormatInt(issue.Index, 10) +} diff --git a/models/repo/repo.go b/models/repo/repo.go index 77e0367a5a..2bb9d0cc44 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -802,3 +802,10 @@ func FixNullArchivedRepository() (int64, error) { IsArchived: false, }) } + +func (r *Repository) GetIRI() string { + if strings.Contains(r.OwnerName, "@") { + return r.OriginalURL + } + return setting.AppURL + "api/v1/activitypub/repo/" + r.OwnerName + "/" + r.Name +} diff --git a/models/user/user.go b/models/user/user.go index 84e2c4a9cc..0949fe3424 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -1339,3 +1339,10 @@ func GetOrderByName() string { } return "name" } + +func (u *User) GetIRI() string { + if u.LoginType == auth.Federated { + return u.LoginName + } + return setting.AppURL + "api/v1/activitypub/user/" + u.Name +} diff --git a/modules/forgefed/forgefed.go b/modules/forgefed/forgefed.go index adafecc100..61962319f5 100644 --- a/modules/forgefed/forgefed.go +++ b/modules/forgefed/forgefed.go @@ -62,35 +62,37 @@ func NotEmpty(i ap.Item) bool { if ap.IsNil(i) { return false } - var notEmpty bool switch i.GetType() { case CommitType: - OnCommit(i, func(c *Commit) error { - notEmpty = ap.NotEmpty(c.Object) - return nil - }) + c, err := ToCommit(i) + if err != nil { + return false + } + return ap.NotEmpty(c.Object) case BranchType: - OnBranch(i, func(b *Branch) error { - notEmpty = ap.NotEmpty(b.Object) - return nil - }) + b, err := ToBranch(i) + if err != nil { + return false + } + return ap.NotEmpty(b.Object) case RepositoryType: - OnRepository(i, func(r *Repository) error { - notEmpty = ap.NotEmpty(r.Actor) - return nil - }) + r, err := ToRepository(i) + if err != nil { + return false + } + return ap.NotEmpty(r.Actor) case PushType: - OnPush(i, func(p *Push) error { - notEmpty = ap.NotEmpty(p.Object) - return nil - }) + p, err := ToPush(i) + if err != nil { + return false + } + return ap.NotEmpty(p.Object) case TicketType: - OnTicket(i, func(t *Ticket) error { - notEmpty = ap.NotEmpty(t.Object) - return nil - }) - default: - notEmpty = ap.NotEmpty(i) + t, err := ToTicket(i) + if err != nil { + return false + } + return ap.NotEmpty(t.Object) } - return notEmpty + return ap.NotEmpty(i) } diff --git a/routers/api/v1/activitypub/authorize_interaction.go b/routers/api/v1/activitypub/authorize_interaction.go index 1dad50abd2..683393d607 100644 --- a/routers/api/v1/activitypub/authorize_interaction.go +++ b/routers/api/v1/activitypub/authorize_interaction.go @@ -42,7 +42,12 @@ func AuthorizeInteraction(ctx *context.Context) { switch object.GetType() { case ap.PersonType: // Federated user - err = createPerson(ctx, object.(*ap.Person)) + person, err := ap.ToActor(object) + if err != nil { + ctx.ServerError("ToActor", err) + return + } + err = createPerson(ctx, person) if err != nil { ctx.ServerError("FederatedUserNew", err) return diff --git a/routers/api/v1/activitypub/create.go b/routers/api/v1/activitypub/create.go index 90f19dfc54..c077ad91ef 100644 --- a/routers/api/v1/activitypub/create.go +++ b/routers/api/v1/activitypub/create.go @@ -67,7 +67,10 @@ func createPerson(ctx context.Context, person *ap.Person) error { } if person.Icon != nil { - icon := person.Icon.(*ap.Image) + icon, err := ap.ToObject(person.Icon) + if err != nil { + return err + } iconURL, err := icon.URL.GetLink().URL() if err != nil { return err @@ -112,7 +115,11 @@ func createPersonFromIRI(ctx context.Context, personIRI ap.IRI) error { } // Create federated user - return createPerson(ctx, object.(*ap.Person)) + person, err := ap.ToActor(object) + if err != nil { + return err + } + return createPerson(ctx, person) } // Create a new federated repo from a Repository object @@ -133,7 +140,8 @@ func createRepository(ctx context.Context, repository *forgefed.Repository) erro } repo, err := repo_service.CreateRepository(user, user, repo_module.CreateRepoOptions{ - Name: repository.Name.String(), + Name: repository.Name.String(), + OriginalURL: repository.GetLink().String(), }) if err != nil { return err @@ -281,7 +289,7 @@ func createComment(ctx context.Context, note *ap.Note) error { return err } - actorUser, err := activitypub.PersonIRIToUser(ctx, note.AttributedTo.GetLink()) + user, err := activitypub.PersonIRIToUser(ctx, note.AttributedTo.GetLink()) if err != nil { return err } @@ -299,7 +307,7 @@ func createComment(ctx context.Context, note *ap.Note) error { return err } _, err = issues_model.CreateCommentCtx(ctx, &issues_model.CreateCommentOptions{ - Doer: actorUser, + Doer: user, Repo: repo, Issue: issue, Content: note.Content.String(), diff --git a/routers/api/v1/activitypub/person.go b/routers/api/v1/activitypub/person.go index 4fd7fa56ff..4fbc7f7360 100644 --- a/routers/api/v1/activitypub/person.go +++ b/routers/api/v1/activitypub/person.go @@ -42,8 +42,8 @@ func Person(ctx *context.APIContext) { // "200": // "$ref": "#/responses/ActivityPub" - link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name - person := ap.PersonNew(ap.IRI(link)) + iri := ctx.ContextUser.GetIRI() + person := ap.PersonNew(ap.IRI(iri)) person.Name = ap.NaturalLanguageValuesNew() err := person.Name.Set("en", ap.Content(ctx.ContextUser.FullName)) @@ -68,14 +68,14 @@ func Person(ctx *context.APIContext) { URL: ap.IRI(ctx.ContextUser.AvatarFullLinkWithSize(2048)), } - person.Inbox = ap.IRI(link + "/inbox") - person.Outbox = ap.IRI(link + "/outbox") - person.Following = ap.IRI(link + "/following") - person.Followers = ap.IRI(link + "/followers") - person.Liked = ap.IRI(link + "/liked") + person.Inbox = ap.IRI(iri + "/inbox") + person.Outbox = ap.IRI(iri + "/outbox") + person.Following = ap.IRI(iri + "/following") + person.Followers = ap.IRI(iri + "/followers") + person.Liked = ap.IRI(iri + "/liked") - person.PublicKey.ID = ap.IRI(link + "#main-key") - person.PublicKey.Owner = ap.IRI(link) + person.PublicKey.ID = ap.IRI(iri + "#main-key") + person.PublicKey.Owner = ap.IRI(iri) publicKeyPem, err := activitypub.GetPublicKey(ctx.ContextUser) if err != nil { ctx.ServerError("GetPublicKey", err) @@ -164,13 +164,13 @@ func PersonOutbox(ctx *context.APIContext) { // "200": // "$ref": "#/responses/ActivityPub" - link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name + iri := ctx.ContextUser.GetIRI() - orderedCollection := ap.OrderedCollectionNew(ap.IRI(link + "/outbox")) - orderedCollection.First = ap.IRI(link + "/outbox?page=1") + orderedCollection := ap.OrderedCollectionNew(ap.IRI(iri + "/outbox")) + orderedCollection.First = ap.IRI(iri + "/outbox?page=1") outbox := ap.OrderedCollectionPageNew(orderedCollection) - outbox.First = ap.IRI(link + "/outbox?page=1") + outbox.First = ap.IRI(iri + "/outbox?page=1") feed, err := activities.GetFeeds(ctx, activities.GetFeedsOptions{ RequestedUser: ctx.ContextUser, @@ -183,12 +183,12 @@ func PersonOutbox(ctx *context.APIContext) { // Only specify next if this amount of feed corresponds to the calculated limit. if len(feed) == convert.ToCorrectPageSize(ctx.FormInt("limit")) { - outbox.Next = ap.IRI(fmt.Sprintf("%s/outbox?page=%d", link, ctx.FormInt("page")+1)) + outbox.Next = ap.IRI(fmt.Sprintf("%s/outbox?page=%d", iri, ctx.FormInt("page")+1)) } // Only specify previous page when there is one. if ctx.FormInt("page") > 1 { - outbox.Prev = ap.IRI(fmt.Sprintf("%s/outbox?page=%d", link, ctx.FormInt("page")-1)) + outbox.Prev = ap.IRI(fmt.Sprintf("%s/outbox?page=%d", iri, ctx.FormInt("page")-1)) } if err != nil { @@ -249,7 +249,7 @@ func PersonFollowing(ctx *context.APIContext) { // "200": // "$ref": "#/responses/ActivityPub" - link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name + iri := ctx.ContextUser.GetIRI() users, _, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, utils.GetListOptions(ctx)) if err != nil { @@ -257,7 +257,7 @@ func PersonFollowing(ctx *context.APIContext) { return } - following := ap.OrderedCollectionNew(ap.IRI(link + "/following")) + following := ap.OrderedCollectionNew(ap.IRI(iri + "/following")) following.TotalItems = uint(len(users)) for _, user := range users { @@ -290,7 +290,7 @@ func PersonFollowers(ctx *context.APIContext) { // "200": // "$ref": "#/responses/ActivityPub" - link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name + iri := ctx.ContextUser.GetIRI() users, _, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, utils.GetListOptions(ctx)) if err != nil { @@ -298,7 +298,7 @@ func PersonFollowers(ctx *context.APIContext) { return } - followers := ap.OrderedCollectionNew(ap.IRI(link + "/followers")) + followers := ap.OrderedCollectionNew(ap.IRI(iri + "/followers")) followers.TotalItems = uint(len(users)) for _, user := range users { @@ -331,7 +331,7 @@ func PersonLiked(ctx *context.APIContext) { // "200": // "$ref": "#/responses/ActivityPub" - link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name + iri := ctx.ContextUser.GetIRI() repos, count, err := repo_model.SearchRepository(&repo_model.SearchRepoOptions{ Actor: ctx.Doer, @@ -343,7 +343,7 @@ func PersonLiked(ctx *context.APIContext) { return } - liked := ap.OrderedCollectionNew(ap.IRI(link + "/liked")) + liked := ap.OrderedCollectionNew(ap.IRI(iri + "/liked")) liked.TotalItems = uint(count) for _, repo := range repos { diff --git a/routers/api/v1/activitypub/repo.go b/routers/api/v1/activitypub/repo.go index c4dae70b3b..33730a76f7 100644 --- a/routers/api/v1/activitypub/repo.go +++ b/routers/api/v1/activitypub/repo.go @@ -39,8 +39,8 @@ func Repo(ctx *context.APIContext) { // "200": // "$ref": "#/responses/ActivityPub" - link := setting.AppURL + "api/v1/activitypub/repo/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name - repo := forgefed.RepositoryNew(ap.IRI(link)) + iri := ctx.Repo.Repository.GetIRI() + repo := forgefed.RepositoryNew(ap.IRI(iri)) repo.Name = ap.NaturalLanguageValuesNew() err := repo.Name.Set("en", ap.Content(ctx.Repo.Repository.Name)) @@ -58,10 +58,10 @@ func Repo(ctx *context.APIContext) { return } - repo.Inbox = ap.IRI(link + "/inbox") - repo.Outbox = ap.IRI(link + "/outbox") - repo.Followers = ap.IRI(link + "/followers") - repo.Team = ap.IRI(link + "/team") + repo.Inbox = ap.IRI(iri + "/inbox") + repo.Outbox = ap.IRI(iri + "/outbox") + repo.Followers = ap.IRI(iri + "/followers") + repo.Team = ap.IRI(iri + "/team") response(ctx, repo) } diff --git a/routers/api/v1/activitypub/ticket.go b/routers/api/v1/activitypub/ticket.go index d20efc12ab..8f6fe1fc1f 100644 --- a/routers/api/v1/activitypub/ticket.go +++ b/routers/api/v1/activitypub/ticket.go @@ -43,12 +43,6 @@ func Ticket(ctx *context.APIContext) { // "200": // "$ref": "#/responses/ActivityPub" - link := setting.AppURL + "api/v1/activitypub/ticket/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name + "/" + ctx.Params("id") - - ticket := forgefed.TicketNew() - ticket.Type = forgefed.TicketType - ticket.ID = ap.IRI(link) - repo, err := repo_model.GetRepositoryByOwnerAndNameCtx(ctx, ctx.ContextUser.Name, ctx.Repo.Repository.Name) if err != nil { ctx.ServerError("GetRepositoryByOwnerAndNameCtx", err) @@ -64,6 +58,12 @@ func Ticket(ctx *context.APIContext) { ctx.ServerError("GetIssueByIndex", err) return } + iri := issue.GetIRI() + + // TODO: move this to services/activitypub/objects.go + ticket := forgefed.TicketNew() + ticket.Type = forgefed.TicketType + ticket.ID = ap.IRI(iri) // Setting a NaturalLanguageValue to a number causes go-ap's JSON parsing to do weird things // Workaround: set it to #1 instead of 1 diff --git a/services/activitypub/follow.go b/services/activitypub/activities.go similarity index 67% rename from services/activitypub/follow.go rename to services/activitypub/activities.go index 1dc32a1252..4aad43e8d7 100644 --- a/services/activitypub/follow.go +++ b/services/activitypub/activities.go @@ -12,42 +12,22 @@ import ( ) // Create and send Follow activity -func Follow(userID, followID int64) error { - followUser, err := user_model.GetUserByID(followID) - if err != nil { - return err - } - - actorUser, err := user_model.GetUserByID(userID) - if err != nil { - return err - } - +func Follow(actorUser, followUser *user_model.User) *ap.Follow { object := ap.PersonNew(ap.IRI(followUser.LoginName)) follow := ap.FollowNew("", object) follow.Type = ap.FollowType follow.Actor = ap.PersonNew(ap.IRI(setting.AppURL + "api/v1/activitypub/user/" + actorUser.Name)) follow.To = ap.ItemCollection{ap.Item(ap.IRI(followUser.LoginName + "/inbox"))} - return Send(actorUser, follow) + return follow } // Create and send Undo Follow activity -func Unfollow(userID, followID int64) error { - followUser, err := user_model.GetUserByID(followID) - if err != nil { - return err - } - - actorUser, err := user_model.GetUserByID(userID) - if err != nil { - return err - } - +func Unfollow(actorUser, followUser *user_model.User) *ap.Undo { object := ap.PersonNew(ap.IRI(followUser.LoginName)) follow := ap.FollowNew("", object) follow.Actor = ap.PersonNew(ap.IRI(setting.AppURL + "api/v1/activitypub/user/" + actorUser.Name)) unfollow := ap.UndoNew("", follow) unfollow.Type = ap.UndoType unfollow.To = ap.ItemCollection{ap.Item(ap.IRI(followUser.LoginName + "/inbox"))} - return Send(actorUser, unfollow) + return unfollow } diff --git a/services/activitypub/objects.go b/services/activitypub/objects.go new file mode 100644 index 0000000000..6758b85d0c --- /dev/null +++ b/services/activitypub/objects.go @@ -0,0 +1,22 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package activitypub + +import ( + issues_model "code.gitea.io/gitea/models/issues" + + ap "github.com/go-ap/activitypub" +) + +func Note(comment *issues_model.Comment) ap.Note { + note := ap.Note{ + Type: ap.NoteType, + AttributedTo: ap.IRI(comment.Poster.GetIRI()), + Context: ap.IRI(comment.Issue.GetIRI()), + } + note.Content = ap.NaturalLanguageValuesNew() + _ = note.Content.Set("en", ap.Content(comment.Content)) + return note +} diff --git a/services/comments/comments.go b/services/comments/comments.go index c40631359b..5d42490ffc 100644 --- a/services/comments/comments.go +++ b/services/comments/comments.go @@ -5,12 +5,17 @@ package comments import ( + "strings" + "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/services/activitypub" + + ap "github.com/go-ap/activitypub" ) // CreateIssueComment creates a plain issue comment. @@ -27,6 +32,20 @@ func CreateIssueComment(doer *user_model.User, repo *repo_model.Repository, issu return nil, err } + if strings.Contains(repo.Owner.Name, "@") { + // Federated comment + // Refactor this to its own function in services/activitypub + create := ap.Create{ + Type: ap.CreateType, + Object: activitypub.Note(comment), + To: ap.ItemCollection{ap.Item(ap.IRI(repo.OriginalURL + "/inbox"))}, + } + err = activitypub.Send(doer, &create) + if err != nil { + return nil, err + } + } + mentions, err := issues_model.FindAndUpdateIssueMentions(db.DefaultContext, issue, doer, comment.Content) if err != nil { return nil, err diff --git a/services/user/user.go b/services/user/user.go index 139f7c7602..8dae88378a 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -295,9 +295,13 @@ func FollowUser(userID, followID int64) (err error) { } if followUser.LoginType == auth.Federated { // Following remote user - err = activitypub.Follow(userID, followID) + actorUser, err := user_model.GetUserByID(userID) if err != nil { - return + return err + } + err = activitypub.Send(actorUser, activitypub.Follow(actorUser, followUser)) + if err != nil { + return err } } @@ -316,9 +320,13 @@ func UnfollowUser(userID, followID int64) (err error) { } if followUser.LoginType == auth.Federated { // Unfollowing remote user - err = activitypub.Unfollow(userID, followID) + actorUser, err := user_model.GetUserByID(userID) if err != nil { - return + return err + } + err = activitypub.Send(actorUser, activitypub.Unfollow(actorUser, followUser)) + if err != nil { + return err } }