From f133e9ca11a233d4284eff885dbe153cb620b77d Mon Sep 17 00:00:00 2001 From: Anthony Wang Date: Fri, 21 Oct 2022 21:06:59 +0000 Subject: [PATCH] Implement sending follow activities I moved around a lot of files to fix import cycles --- modules/activitypub/comment.go | 2 +- modules/activitypub/follow.go | 8 +-- modules/activitypub/fork.go | 4 +- modules/activitypub/iri.go | 12 ++--- modules/activitypub/pull_request.go | 2 +- modules/activitypub/repo.go | 5 +- modules/activitypub/star.go | 4 +- routers/api/v1/activitypub/reqsignature.go | 3 +- routers/api/v1/user/follower.go | 5 +- .../web}/authorize_interaction.go | 16 +++--- routers/web/user/profile.go | 5 +- routers/web/web.go | 3 +- .../user.go => services/user/activitypub.go | 50 +++++++++++++++++-- 13 files changed, 81 insertions(+), 38 deletions(-) rename {modules/activitypub => routers/web}/authorize_interaction.go (78%) rename modules/activitypub/user.go => services/user/activitypub.go (55%) diff --git a/modules/activitypub/comment.go b/modules/activitypub/comment.go index b5ea350347..7e36bed764 100644 --- a/modules/activitypub/comment.go +++ b/modules/activitypub/comment.go @@ -17,7 +17,7 @@ import ( // Create a comment func Comment(ctx context.Context, note *ap.Note) error { - actorUser, err := personIRIToUser(ctx, note.AttributedTo.GetLink()) + actorUser, err := PersonIRIToUser(ctx, note.AttributedTo.GetLink()) if err != nil { return err } diff --git a/modules/activitypub/follow.go b/modules/activitypub/follow.go index 9db9e534cd..a36404810a 100644 --- a/modules/activitypub/follow.go +++ b/modules/activitypub/follow.go @@ -17,14 +17,14 @@ import ( func Follow(ctx context.Context, follow ap.Follow) error { // Actor is the user performing the follow actorIRI := follow.Actor.GetLink() - actorUser, err := personIRIToUser(ctx, actorIRI) + actorUser, err := PersonIRIToUser(ctx, actorIRI) if err != nil { return err } // Object is the user being followed objectIRI := follow.Object.GetLink() - objectUser, err := personIRIToUser(ctx, objectIRI) + objectUser, err := PersonIRIToUser(ctx, objectIRI) // Must be a local user if err != nil || strings.Contains(objectUser.Name, "@") { return err @@ -48,14 +48,14 @@ func Unfollow(ctx context.Context, unfollow ap.Undo) error { follow := unfollow.Object.(*ap.Follow) // Actor is the user performing the undo follow actorIRI := follow.Actor.GetLink() - actorUser, err := personIRIToUser(ctx, actorIRI) + actorUser, err := PersonIRIToUser(ctx, actorIRI) if err != nil { return err } // Object is the user being unfollowed objectIRI := follow.Object.GetLink() - objectUser, err := personIRIToUser(ctx, objectIRI) + objectUser, err := PersonIRIToUser(ctx, objectIRI) // Must be a local user if err != nil || strings.Contains(objectUser.Name, "@") { return err diff --git a/modules/activitypub/fork.go b/modules/activitypub/fork.go index 1868c05233..bf41a342a4 100644 --- a/modules/activitypub/fork.go +++ b/modules/activitypub/fork.go @@ -52,7 +52,7 @@ func ReceiveFork(ctx context.Context, create ap.Create) error { repository := create.Object.(*forgefed.Repository) - actor, err := personIRIToUser(ctx, create.Actor.GetLink()) + actor, err := PersonIRIToUser(ctx, create.Actor.GetLink()) if err != nil { return err } @@ -62,7 +62,7 @@ func ReceiveFork(ctx context.Context, create ap.Create) error { // Create the fork repoIRI := repository.GetLink() - username, reponame, err := repositoryIRIToName(repoIRI) + username, reponame, err := RepositoryIRIToName(repoIRI) if err != nil { return err } diff --git a/modules/activitypub/iri.go b/modules/activitypub/iri.go index 235545cd20..b546202c27 100644 --- a/modules/activitypub/iri.go +++ b/modules/activitypub/iri.go @@ -17,7 +17,7 @@ import ( ) // Returns the username corresponding to a Person actor IRI -func personIRIToName(personIRI ap.IRI) (string, error) { +func PersonIRIToName(personIRI ap.IRI) (string, error) { personIRISplit := strings.Split(personIRI.String(), "/") if len(personIRISplit) < 3 { return "", errors.New("Not a Person actor IRI") @@ -35,8 +35,8 @@ func personIRIToName(personIRI ap.IRI) (string, error) { } // 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) +func PersonIRIToUser(ctx context.Context, personIRI ap.IRI) (*user_model.User, error) { + name, err := PersonIRIToName(personIRI) if err != nil { return nil, err } @@ -50,7 +50,7 @@ func personIRIToUser(ctx context.Context, personIRI ap.IRI) (*user_model.User, e } // Returns the owner and name corresponding to a Repository actor IRI -func repositoryIRIToName(repoIRI ap.IRI) (string, string, error) { +func RepositoryIRIToName(repoIRI ap.IRI) (string, string, error) { repoIRISplit := strings.Split(repoIRI.String(), "/") if len(repoIRISplit) < 5 { return "", "", errors.New("Not a Repository actor IRI") @@ -68,8 +68,8 @@ func repositoryIRIToName(repoIRI ap.IRI) (string, string, error) { } // Returns the repository corresponding to a Repository actor IRI -func repositoryIRIToRepository(ctx context.Context, repoIRI ap.IRI) (*repo_model.Repository, error) { - username, reponame, err := repositoryIRIToName(repoIRI) +func RepositoryIRIToRepository(ctx context.Context, repoIRI ap.IRI) (*repo_model.Repository, error) { + username, reponame, err := RepositoryIRIToName(repoIRI) if err != nil { return nil, err } diff --git a/modules/activitypub/pull_request.go b/modules/activitypub/pull_request.go index 3ed766a282..c6e86c482b 100644 --- a/modules/activitypub/pull_request.go +++ b/modules/activitypub/pull_request.go @@ -18,7 +18,7 @@ import ( func PullRequest(ctx context.Context, ticket *forgefed.Ticket) error { // TODO: Clean this up - actorUser, err := personIRIToUser(ctx, ticket.AttributedTo.GetLink()) + actorUser, err := PersonIRIToUser(ctx, ticket.AttributedTo.GetLink()) if err != nil { log.Warn("Couldn't find ticket actor user", err) } diff --git a/modules/activitypub/repo.go b/modules/activitypub/repo.go index cf6f7bf7ce..98b5ab4f7e 100644 --- a/modules/activitypub/repo.go +++ b/modules/activitypub/repo.go @@ -16,7 +16,7 @@ import ( // Create a new federated repo from a Repository object func FederatedRepoNew(ctx context.Context, repository *forgefed.Repository) error { ownerIRI := repository.AttributedTo.GetLink() - user, err := personIRIToUser(ctx, ownerIRI) + user, err := PersonIRIToUser(ctx, ownerIRI) if err != nil { return err } @@ -25,7 +25,6 @@ func FederatedRepoNew(ctx context.Context, repository *forgefed.Repository) erro if err == nil { return nil } - repo, err := repo_service.CreateRepository(user, user, repo_module.CreateRepoOptions{ Name: repository.Name.String(), @@ -36,7 +35,7 @@ func FederatedRepoNew(ctx context.Context, repository *forgefed.Repository) erro if repository.ForkedFrom != nil { repo.IsFork = true - forkedFrom, err := repositoryIRIToRepository(ctx, repository.ForkedFrom.GetLink()) + forkedFrom, err := RepositoryIRIToRepository(ctx, repository.ForkedFrom.GetLink()) if err != nil { return err } diff --git a/modules/activitypub/star.go b/modules/activitypub/star.go index 67f16b38b4..3b85ca5a1f 100644 --- a/modules/activitypub/star.go +++ b/modules/activitypub/star.go @@ -15,11 +15,11 @@ import ( // Process a Like activity to star a repository func ReceiveStar(ctx context.Context, like ap.Like) (err error) { - user, err := personIRIToUser(ctx, like.Actor.GetLink()) + user, err := PersonIRIToUser(ctx, like.Actor.GetLink()) if err != nil { return } - repo, err := repositoryIRIToRepository(ctx, like.Object.GetLink()) + repo, err := RepositoryIRIToRepository(ctx, like.Object.GetLink()) if err != nil || strings.Contains(repo.Name, "@") || repo.IsPrivate { return } diff --git a/routers/api/v1/activitypub/reqsignature.go b/routers/api/v1/activitypub/reqsignature.go index 16731c485a..555e060ade 100644 --- a/routers/api/v1/activitypub/reqsignature.go +++ b/routers/api/v1/activitypub/reqsignature.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/activitypub" gitea_context "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" + user_service "code.gitea.io/gitea/services/user" ap "github.com/go-ap/activitypub" "github.com/go-fed/httpsig" @@ -84,7 +85,7 @@ func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, er return } - err = activitypub.FederatedUserNew(ctx, &person) + err = user_service.FederatedUserNew(ctx, &person) return authenticated, err } diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go index 22f8f40e1c..f0109d7d6e 100644 --- a/routers/api/v1/user/follower.go +++ b/routers/api/v1/user/follower.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/api/v1/utils" + user_service "code.gitea.io/gitea/services/user" ) func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) { @@ -219,7 +220,7 @@ func Follow(ctx *context.APIContext) { // "204": // "$ref": "#/responses/empty" - if err := user_model.FollowUser(ctx.Doer.ID, ctx.ContextUser.ID); err != nil { + if err := user_service.FollowUser(ctx.Doer.ID, ctx.ContextUser.ID); err != nil { ctx.Error(http.StatusInternalServerError, "FollowUser", err) return } @@ -241,7 +242,7 @@ func Unfollow(ctx *context.APIContext) { // "204": // "$ref": "#/responses/empty" - if err := user_model.UnfollowUser(ctx.Doer.ID, ctx.ContextUser.ID); err != nil { + if err := user_service.UnfollowUser(ctx.Doer.ID, ctx.ContextUser.ID); err != nil { ctx.Error(http.StatusInternalServerError, "UnfollowUser", err) return } diff --git a/modules/activitypub/authorize_interaction.go b/routers/web/authorize_interaction.go similarity index 78% rename from modules/activitypub/authorize_interaction.go rename to routers/web/authorize_interaction.go index c982ce05ad..3679adef8a 100644 --- a/modules/activitypub/authorize_interaction.go +++ b/routers/web/authorize_interaction.go @@ -2,14 +2,16 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package activitypub +package web import ( "net/http" "net/url" + "code.gitea.io/gitea/modules/activitypub" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/forgefed" + user_service "code.gitea.io/gitea/services/user" ap "github.com/go-ap/activitypub" ) @@ -20,7 +22,7 @@ func AuthorizeInteraction(ctx *context.Context) { ctx.ServerError("Could not parse URI", err) return } - resp, err := Fetch(uri) + resp, err := activitypub.Fetch(uri) if err != nil { ctx.ServerError("Fetch", err) return @@ -40,12 +42,12 @@ func AuthorizeInteraction(ctx *context.Context) { ctx.ServerError("UnmarshalJSON", err) return } - err = FederatedUserNew(ctx, object.(*ap.Person)) + err = user_service.FederatedUserNew(ctx, object.(*ap.Person)) if err != nil { ctx.ServerError("FederatedUserNew", err) return } - name, err := personIRIToName(object.GetLink()) + name, err := activitypub.PersonIRIToName(object.GetLink()) if err != nil { ctx.ServerError("personIRIToName", err) return @@ -53,13 +55,13 @@ func AuthorizeInteraction(ctx *context.Context) { ctx.Redirect(name) case forgefed.RepositoryType: err = forgefed.OnRepository(object, func(r *forgefed.Repository) error { - return FederatedRepoNew(ctx, r) + return activitypub.FederatedRepoNew(ctx, r) }) if err != nil { ctx.ServerError("FederatedRepoNew", err) return } - username, reponame, err := repositoryIRIToName(object.GetLink()) + username, reponame, err := activitypub.RepositoryIRIToName(object.GetLink()) if err != nil { ctx.ServerError("repositoryIRIToName", err) return @@ -67,7 +69,7 @@ func AuthorizeInteraction(ctx *context.Context) { ctx.Redirect(username + "/" + reponame) case forgefed.TicketType: err = forgefed.OnTicket(object, func(t *forgefed.Ticket) error { - return ReceiveIssue(ctx, t) + return activitypub.ReceiveIssue(ctx, t) }) if err != nil { ctx.ServerError("ReceiveIssue", err) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 6e16b377db..bc56407e55 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -23,6 +23,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/web/feed" "code.gitea.io/gitea/routers/web/org" + user_service "code.gitea.io/gitea/services/user" ) // Profile render user's profile page @@ -302,9 +303,9 @@ func Action(ctx *context.Context) { var err error switch ctx.FormString("action") { case "follow": - err = user_model.FollowUser(ctx.Doer.ID, ctx.ContextUser.ID) + err = user_service.FollowUser(ctx.Doer.ID, ctx.ContextUser.ID) case "unfollow": - err = user_model.UnfollowUser(ctx.Doer.ID, ctx.ContextUser.ID) + err = user_service.UnfollowUser(ctx.Doer.ID, ctx.ContextUser.ID) } if err != nil { diff --git a/routers/web/web.go b/routers/web/web.go index c741b4b602..1b0d0ec1a4 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -12,7 +12,6 @@ import ( "code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/unit" - "code.gitea.io/gitea/modules/activitypub" "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" @@ -1317,7 +1316,7 @@ func RegisterRoutes(m *web.Route) { }, reqSignIn) if setting.Federation.Enabled { - m.Get("/authorize_interaction", activitypub.AuthorizeInteraction) + m.Get("/authorize_interaction", AuthorizeInteraction) } if setting.API.EnableSwagger { diff --git a/modules/activitypub/user.go b/services/user/activitypub.go similarity index 55% rename from modules/activitypub/user.go rename to services/user/activitypub.go index 89732044ca..e1c95b5675 100644 --- a/modules/activitypub/user.go +++ b/services/user/activitypub.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package activitypub +package user import ( "context" @@ -11,15 +11,55 @@ import ( "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/activitypub" "code.gitea.io/gitea/modules/setting" - user_service "code.gitea.io/gitea/services/user" ap "github.com/go-ap/activitypub" ) +// FollowUser marks someone be another's follower. +func FollowUser(userID, followID int64) (err error) { + if userID == followID || user_model.IsFollowing(userID, followID) { + return nil + } + + followUser, err := user_model.GetUserByID(followID) + if err != nil { + return err + } + if followUser.LoginType == auth.Federated { + // Following remote user + actorUser, err := user_model.GetUserByID(userID) + if err != nil { + return err + } + + 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"))} + err = activitypub.Send(actorUser, follow) + if err != nil { + return err + } + } + + return user_model.FollowUser(userID, followID) +} + +// UnfollowUser unmarks someone as another's follower. +func UnfollowUser(userID, followID int64) (err error) { + if userID == followID || !user_model.IsFollowing(userID, followID) { + return nil + } + + return user_model.UnfollowUser(userID, followID) +} + // Create a new federated user from a Person object func FederatedUserNew(ctx context.Context, person *ap.Person) error { - name, err := personIRIToName(person.GetLink()) + name, err := activitypub.PersonIRIToName(person.GetLink()) if err != nil { return err } @@ -63,12 +103,12 @@ func FederatedUserNew(ctx context.Context, person *ap.Person) error { return err } - body, err := Fetch(iconURL) + body, err := activitypub.Fetch(iconURL) if err != nil { return err } - err = user_service.UploadAvatar(user, body) + err = UploadAvatar(user, body) if err != nil { return err }