Implement sending follow activities

I moved around a lot of files to fix import cycles
This commit is contained in:
Anthony Wang 2022-10-21 21:06:59 +00:00
parent e78dd699de
commit f133e9ca11
Signed by: a
GPG key ID: 42A5B952E6DD8D38
13 changed files with 81 additions and 38 deletions

View file

@ -17,7 +17,7 @@ import (
// Create a comment // Create a comment
func Comment(ctx context.Context, note *ap.Note) error { 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 { if err != nil {
return err return err
} }

View file

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

View file

@ -52,7 +52,7 @@ func ReceiveFork(ctx context.Context, create ap.Create) error {
repository := create.Object.(*forgefed.Repository) repository := create.Object.(*forgefed.Repository)
actor, err := personIRIToUser(ctx, create.Actor.GetLink()) actor, err := PersonIRIToUser(ctx, create.Actor.GetLink())
if err != nil { if err != nil {
return err return err
} }
@ -62,7 +62,7 @@ func ReceiveFork(ctx context.Context, create ap.Create) error {
// Create the fork // Create the fork
repoIRI := repository.GetLink() repoIRI := repository.GetLink()
username, reponame, err := repositoryIRIToName(repoIRI) username, reponame, err := RepositoryIRIToName(repoIRI)
if err != nil { if err != nil {
return err return err
} }

View file

@ -17,7 +17,7 @@ import (
) )
// Returns the username corresponding to a Person actor IRI // 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(), "/") personIRISplit := strings.Split(personIRI.String(), "/")
if len(personIRISplit) < 3 { if len(personIRISplit) < 3 {
return "", errors.New("Not a Person actor IRI") 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 // Returns the user corresponding to a Person actor IRI
func personIRIToUser(ctx context.Context, personIRI ap.IRI) (*user_model.User, error) { func PersonIRIToUser(ctx context.Context, personIRI ap.IRI) (*user_model.User, error) {
name, err := personIRIToName(personIRI) name, err := PersonIRIToName(personIRI)
if err != nil { if err != nil {
return nil, err 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 // 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(), "/") repoIRISplit := strings.Split(repoIRI.String(), "/")
if len(repoIRISplit) < 5 { if len(repoIRISplit) < 5 {
return "", "", errors.New("Not a Repository actor IRI") 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 // Returns the repository corresponding to a Repository actor IRI
func repositoryIRIToRepository(ctx context.Context, repoIRI ap.IRI) (*repo_model.Repository, error) { func RepositoryIRIToRepository(ctx context.Context, repoIRI ap.IRI) (*repo_model.Repository, error) {
username, reponame, err := repositoryIRIToName(repoIRI) username, reponame, err := RepositoryIRIToName(repoIRI)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -18,7 +18,7 @@ import (
func PullRequest(ctx context.Context, ticket *forgefed.Ticket) error { func PullRequest(ctx context.Context, ticket *forgefed.Ticket) error {
// TODO: Clean this up // TODO: Clean this up
actorUser, err := personIRIToUser(ctx, ticket.AttributedTo.GetLink()) actorUser, err := PersonIRIToUser(ctx, ticket.AttributedTo.GetLink())
if err != nil { if err != nil {
log.Warn("Couldn't find ticket actor user", err) log.Warn("Couldn't find ticket actor user", err)
} }

View file

@ -16,7 +16,7 @@ import (
// Create a new federated repo from a Repository object // Create a new federated repo from a Repository object
func FederatedRepoNew(ctx context.Context, repository *forgefed.Repository) error { func FederatedRepoNew(ctx context.Context, repository *forgefed.Repository) error {
ownerIRI := repository.AttributedTo.GetLink() ownerIRI := repository.AttributedTo.GetLink()
user, err := personIRIToUser(ctx, ownerIRI) user, err := PersonIRIToUser(ctx, ownerIRI)
if err != nil { if err != nil {
return err return err
} }
@ -25,7 +25,6 @@ func FederatedRepoNew(ctx context.Context, repository *forgefed.Repository) erro
if err == nil { if err == nil {
return nil return nil
} }
repo, err := repo_service.CreateRepository(user, user, repo_module.CreateRepoOptions{ repo, err := repo_service.CreateRepository(user, user, repo_module.CreateRepoOptions{
Name: repository.Name.String(), Name: repository.Name.String(),
@ -36,7 +35,7 @@ func FederatedRepoNew(ctx context.Context, repository *forgefed.Repository) erro
if repository.ForkedFrom != nil { if repository.ForkedFrom != nil {
repo.IsFork = true repo.IsFork = true
forkedFrom, err := repositoryIRIToRepository(ctx, repository.ForkedFrom.GetLink()) forkedFrom, err := RepositoryIRIToRepository(ctx, repository.ForkedFrom.GetLink())
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,11 +15,11 @@ import (
// Process a Like activity to star a repository // Process a Like activity to star a repository
func ReceiveStar(ctx context.Context, like ap.Like) (err error) { 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 { if err != nil {
return return
} }
repo, err := repositoryIRIToRepository(ctx, like.Object.GetLink()) repo, err := RepositoryIRIToRepository(ctx, like.Object.GetLink())
if err != nil || strings.Contains(repo.Name, "@") || repo.IsPrivate { if err != nil || strings.Contains(repo.Name, "@") || repo.IsPrivate {
return return
} }

View file

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/activitypub" "code.gitea.io/gitea/modules/activitypub"
gitea_context "code.gitea.io/gitea/modules/context" gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
user_service "code.gitea.io/gitea/services/user"
ap "github.com/go-ap/activitypub" ap "github.com/go-ap/activitypub"
"github.com/go-fed/httpsig" "github.com/go-fed/httpsig"
@ -84,7 +85,7 @@ func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, er
return return
} }
err = activitypub.FederatedUserNew(ctx, &person) err = user_service.FederatedUserNew(ctx, &person)
return authenticated, err return authenticated, err
} }

View file

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils" "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) { func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) {
@ -219,7 +220,7 @@ func Follow(ctx *context.APIContext) {
// "204": // "204":
// "$ref": "#/responses/empty" // "$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) ctx.Error(http.StatusInternalServerError, "FollowUser", err)
return return
} }
@ -241,7 +242,7 @@ func Unfollow(ctx *context.APIContext) {
// "204": // "204":
// "$ref": "#/responses/empty" // "$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) ctx.Error(http.StatusInternalServerError, "UnfollowUser", err)
return return
} }

View file

@ -2,14 +2,16 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package activitypub package web
import ( import (
"net/http" "net/http"
"net/url" "net/url"
"code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/forgefed" "code.gitea.io/gitea/modules/forgefed"
user_service "code.gitea.io/gitea/services/user"
ap "github.com/go-ap/activitypub" ap "github.com/go-ap/activitypub"
) )
@ -20,7 +22,7 @@ func AuthorizeInteraction(ctx *context.Context) {
ctx.ServerError("Could not parse URI", err) ctx.ServerError("Could not parse URI", err)
return return
} }
resp, err := Fetch(uri) resp, err := activitypub.Fetch(uri)
if err != nil { if err != nil {
ctx.ServerError("Fetch", err) ctx.ServerError("Fetch", err)
return return
@ -40,12 +42,12 @@ func AuthorizeInteraction(ctx *context.Context) {
ctx.ServerError("UnmarshalJSON", err) ctx.ServerError("UnmarshalJSON", err)
return return
} }
err = FederatedUserNew(ctx, object.(*ap.Person)) err = user_service.FederatedUserNew(ctx, object.(*ap.Person))
if err != nil { if err != nil {
ctx.ServerError("FederatedUserNew", err) ctx.ServerError("FederatedUserNew", err)
return return
} }
name, err := personIRIToName(object.GetLink()) name, err := activitypub.PersonIRIToName(object.GetLink())
if err != nil { if err != nil {
ctx.ServerError("personIRIToName", err) ctx.ServerError("personIRIToName", err)
return return
@ -53,13 +55,13 @@ func AuthorizeInteraction(ctx *context.Context) {
ctx.Redirect(name) ctx.Redirect(name)
case forgefed.RepositoryType: case forgefed.RepositoryType:
err = forgefed.OnRepository(object, func(r *forgefed.Repository) error { err = forgefed.OnRepository(object, func(r *forgefed.Repository) error {
return FederatedRepoNew(ctx, r) return activitypub.FederatedRepoNew(ctx, r)
}) })
if err != nil { if err != nil {
ctx.ServerError("FederatedRepoNew", err) ctx.ServerError("FederatedRepoNew", err)
return return
} }
username, reponame, err := repositoryIRIToName(object.GetLink()) username, reponame, err := activitypub.RepositoryIRIToName(object.GetLink())
if err != nil { if err != nil {
ctx.ServerError("repositoryIRIToName", err) ctx.ServerError("repositoryIRIToName", err)
return return
@ -67,7 +69,7 @@ func AuthorizeInteraction(ctx *context.Context) {
ctx.Redirect(username + "/" + reponame) ctx.Redirect(username + "/" + reponame)
case forgefed.TicketType: case forgefed.TicketType:
err = forgefed.OnTicket(object, func(t *forgefed.Ticket) error { err = forgefed.OnTicket(object, func(t *forgefed.Ticket) error {
return ReceiveIssue(ctx, t) return activitypub.ReceiveIssue(ctx, t)
}) })
if err != nil { if err != nil {
ctx.ServerError("ReceiveIssue", err) ctx.ServerError("ReceiveIssue", err)

View file

@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/feed" "code.gitea.io/gitea/routers/web/feed"
"code.gitea.io/gitea/routers/web/org" "code.gitea.io/gitea/routers/web/org"
user_service "code.gitea.io/gitea/services/user"
) )
// Profile render user's profile page // Profile render user's profile page
@ -302,9 +303,9 @@ func Action(ctx *context.Context) {
var err error var err error
switch ctx.FormString("action") { switch ctx.FormString("action") {
case "follow": case "follow":
err = user_model.FollowUser(ctx.Doer.ID, ctx.ContextUser.ID) err = user_service.FollowUser(ctx.Doer.ID, ctx.ContextUser.ID)
case "unfollow": 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 { if err != nil {

View file

@ -12,7 +12,6 @@ import (
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -1317,7 +1316,7 @@ func RegisterRoutes(m *web.Route) {
}, reqSignIn) }, reqSignIn)
if setting.Federation.Enabled { if setting.Federation.Enabled {
m.Get("/authorize_interaction", activitypub.AuthorizeInteraction) m.Get("/authorize_interaction", AuthorizeInteraction)
} }
if setting.API.EnableSwagger { if setting.API.EnableSwagger {

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package activitypub package user
import ( import (
"context" "context"
@ -11,15 +11,55 @@ import (
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
user_service "code.gitea.io/gitea/services/user"
ap "github.com/go-ap/activitypub" 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 // Create a new federated user from a Person object
func FederatedUserNew(ctx context.Context, person *ap.Person) error { func FederatedUserNew(ctx context.Context, person *ap.Person) error {
name, err := personIRIToName(person.GetLink()) name, err := activitypub.PersonIRIToName(person.GetLink())
if err != nil { if err != nil {
return err return err
} }
@ -63,12 +103,12 @@ func FederatedUserNew(ctx context.Context, person *ap.Person) error {
return err return err
} }
body, err := Fetch(iconURL) body, err := activitypub.Fetch(iconURL)
if err != nil { if err != nil {
return err return err
} }
err = user_service.UploadAvatar(user, body) err = UploadAvatar(user, body)
if err != nil { if err != nil {
return err return err
} }