Move AS object processing to routers/api/v1/activitypub, move AP transport and IRI code to services/activitypub

This is to follow https://docs.gitea.io/en-us/guidelines-backend/ and avoid import cycles.
This commit is contained in:
Anthony Wang 2022-11-27 00:34:24 +00:00
parent 41e9a10763
commit fd4d0e730e
Signed by: a
GPG key ID: 42A5B952E6DD8D38
29 changed files with 392 additions and 376 deletions

View file

@ -1,42 +0,0 @@
// 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 (
"context"
"code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
ap "github.com/go-ap/activitypub"
)
// Create a comment
func Comment(ctx context.Context, note *ap.Note) error {
actorUser, err := PersonIRIToUser(ctx, note.AttributedTo.GetLink())
if err != nil {
return err
}
username, reponame, idx, err := TicketIRIToName(note.Context.GetLink())
if err != nil {
return err
}
repo, err := repo_model.GetRepositoryByOwnerAndNameCtx(ctx, username, reponame)
if err != nil {
return err
}
issue, err := issues.GetIssueByIndex(repo.ID, idx)
if err != nil {
return err
}
_, err = issues.CreateCommentCtx(ctx, &issues.CreateCommentOptions{
Doer: actorUser,
Repo: repo,
Issue: issue,
Content: note.Content.String(),
})
return err
}

View file

@ -1,47 +0,0 @@
// 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 (
"context"
"fmt"
"strconv"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/forgefed"
issue_service "code.gitea.io/gitea/services/issue"
ap "github.com/go-ap/activitypub"
)
// Create an issue
func ReceiveIssue(ctx context.Context, ticket *forgefed.Ticket) error {
// Construct issue
user, err := PersonIRIToUser(ctx, ap.IRI(ticket.AttributedTo.GetLink().String()))
if err != nil {
return err
}
repo, err := RepositoryIRIToRepository(ctx, ap.IRI(ticket.Context.GetLink().String()))
if err != nil {
return err
}
fmt.Println(ticket)
fmt.Println(ticket.Name.String())
idx, err := strconv.ParseInt(ticket.Name.String()[1:], 10, 64)
if err != nil {
return err
}
issue := &issues_model.Issue{
ID: idx,
RepoID: repo.ID,
Repo: repo,
Title: ticket.Summary.String(),
PosterID: user.ID,
Poster: user,
Content: ticket.Content.String(),
}
fmt.Println(issue)
return issue_service.NewIssue(repo, issue, nil, nil, nil)
}

View file

@ -1,44 +0,0 @@
// 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 (
"context"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/forgefed"
repo_module "code.gitea.io/gitea/modules/repository"
repo_service "code.gitea.io/gitea/services/repository"
)
// Create a new federated repo from a Repository object
func FederatedRepoNew(ctx context.Context, repository *forgefed.Repository) error {
user, err := PersonIRIToUser(ctx, repository.AttributedTo.GetLink())
if err != nil {
return err
}
_, err = repo_model.GetRepositoryByOwnerAndNameCtx(ctx, user.Name, repository.Name.String())
if err == nil {
return nil
}
repo, err := repo_service.CreateRepository(user, user, repo_module.CreateRepoOptions{
Name: repository.Name.String(),
})
if err != nil {
return err
}
if repository.ForkedFrom != nil {
repo.IsFork = true
forkedFrom, err := RepositoryIRIToRepository(ctx, repository.ForkedFrom.GetLink())
if err != nil {
return err
}
repo.ForkID = forkedFrom.ID
}
return nil
}

View file

@ -2,17 +2,16 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package web
package activitypub
import (
"net/http"
"net/url"
"strconv"
"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"
"code.gitea.io/gitea/services/activitypub"
ap "github.com/go-ap/activitypub"
)
@ -45,7 +44,7 @@ func AuthorizeInteraction(ctx *context.Context) {
ctx.ServerError("UnmarshalJSON", err)
return
}
err = user_service.FederatedUserNew(ctx, object.(*ap.Person))
err = createPerson(ctx, object.(*ap.Person))
if err != nil {
ctx.ServerError("FederatedUserNew", err)
return
@ -59,29 +58,7 @@ func AuthorizeInteraction(ctx *context.Context) {
case forgefed.RepositoryType:
// Federated repository
err = forgefed.OnRepository(object, func(r *forgefed.Repository) error {
ownerURL, err := url.Parse(r.AttributedTo.GetLink().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
err = user_service.FederatedUserNew(ctx, object.(*ap.Person))
if err != nil {
return err
}
return activitypub.FederatedRepoNew(ctx, r)
return createRepository(ctx, r)
})
if err != nil {
ctx.ServerError("FederatedRepoNew", err)
@ -117,12 +94,12 @@ func AuthorizeInteraction(ctx *context.Context) {
}
// Create federated repo
err = forgefed.OnRepository(object, func(r *forgefed.Repository) error {
return activitypub.FederatedRepoNew(ctx, r)
return createRepository(ctx, r)
})
if err != nil {
return err
}
return activitypub.ReceiveIssue(ctx, t)
return createIssue(ctx, t)
})
if err != nil {
ctx.ServerError("ReceiveIssue", err)

View file

@ -0,0 +1,199 @@
// 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 (
"context"
"errors"
"net/url"
"strconv"
"strings"
"code.gitea.io/gitea/models/auth"
issue_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/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"
repo_service "code.gitea.io/gitea/services/repository"
user_service "code.gitea.io/gitea/services/user"
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 {
return err
}
exists, err := user_model.IsUserExist(ctx, 0, name)
if err != nil {
return err
}
if exists {
return nil
}
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{
Name: name,
FullName: person.Name.String(), // May not exist!!
Email: email,
LoginType: auth.Federated,
LoginName: person.GetLink().String(),
}
err = user_model.CreateUser(user)
if err != nil {
return err
}
if person.Icon != nil {
icon := person.Icon.(*ap.Image)
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
}
return user_model.SetUserSetting(user.ID, user_model.UserActivityPubPubPem, person.PublicKey.PublicKeyPem)
}
// Create a new federated repo from a Repository object
func createRepository(ctx context.Context, repository *forgefed.Repository) error {
ownerURL, err := url.Parse(repository.AttributedTo.GetLink().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
err = createPerson(ctx, object.(*ap.Person))
if err != nil {
return err
}
user, err := activitypub.PersonIRIToUser(ctx, repository.AttributedTo.GetLink())
if err != nil {
return err
}
_, err = repo_model.GetRepositoryByOwnerAndNameCtx(ctx, user.Name, repository.Name.String())
if err == nil {
return nil
}
repo, err := repo_service.CreateRepository(user, user, repo_module.CreateRepoOptions{
Name: repository.Name.String(),
})
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
}
// Create an issue
func createIssue(ctx context.Context, ticket *forgefed.Ticket) error {
// Construct issue
user, err := activitypub.PersonIRIToUser(ctx, ap.IRI(ticket.AttributedTo.GetLink().String()))
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
}
issue := &issue_model.Issue{
ID: idx,
RepoID: repo.ID,
Repo: repo,
Title: ticket.Summary.String(),
PosterID: user.ID,
Poster: user,
Content: ticket.Content.String(),
}
return issue_service.NewIssue(repo, issue, nil, nil, nil)
}
// Create a comment
func createComment(ctx context.Context, note *ap.Note) error {
actorUser, err := activitypub.PersonIRIToUser(ctx, note.AttributedTo.GetLink())
if err != nil {
return err
}
username, reponame, idx, err := activitypub.TicketIRIToName(note.Context.GetLink())
if err != nil {
return err
}
repo, err := repo_model.GetRepositoryByOwnerAndNameCtx(ctx, username, reponame)
if err != nil {
return err
}
issue, err := issue_model.GetIssueByIndex(repo.ID, idx)
if err != nil {
return err
}
_, err = issue_model.CreateCommentCtx(ctx, &issue_model.CreateCommentOptions{
Doer: actorUser,
Repo: repo,
Issue: issue,
Content: note.Content.String(),
})
return err
}

View file

@ -9,22 +9,23 @@ import (
"strings"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/services/activitypub"
ap "github.com/go-ap/activitypub"
)
// Process a Follow activity
func Follow(ctx context.Context, follow ap.Follow) error {
// Process an incoming Follow activity
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 := activitypub.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 := activitypub.PersonIRIToUser(ctx, objectIRI)
// Must be a local user
if err != nil || strings.Contains(objectUser.Name, "@") {
return err
@ -40,22 +41,27 @@ func Follow(ctx context.Context, follow ap.Follow) error {
accept.Actor = ap.Person{ID: objectIRI}
accept.To = ap.ItemCollection{ap.IRI(actorIRI.String() + "/inbox")}
accept.Object = follow
return Send(objectUser, accept)
return activitypub.Send(objectUser, accept)
}
// Process a Undo follow activity
func Unfollow(ctx context.Context, unfollow ap.Undo) error {
follow := unfollow.Object.(*ap.Follow)
// Process an incoming Undo follow activity
func unfollow(ctx context.Context, unfollow ap.Undo) error {
// Object contains the follow
follow, err := ap.To[ap.Follow](unfollow.Object)
if err != nil {
return err
}
// Actor is the user performing the undo follow
actorIRI := follow.Actor.GetLink()
actorUser, err := PersonIRIToUser(ctx, actorIRI)
actorUser, err := activitypub.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 := activitypub.PersonIRIToUser(ctx, objectIRI)
// Must be a local user
if err != nil || strings.Contains(objectUser.Name, "@") {
return err

View file

@ -0,0 +1,51 @@
// 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 (
"context"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/forgefed"
"code.gitea.io/gitea/services/activitypub"
repo_service "code.gitea.io/gitea/services/repository"
ap "github.com/go-ap/activitypub"
)
func fork(ctx context.Context, create ap.Create) error {
// Object is the new fork repository
repository, err := ap.To[forgefed.Repository](create.Object)
if err != nil {
return nil
}
// TODO: Clean this up
actor, err := activitypub.PersonIRIToUser(ctx, create.Actor.GetLink())
if err != nil {
return err
}
// Don't create an actual copy of the remote repo!
// https://gitea.com/xy/gitea/issues/7
// Create the fork
repoIRI := repository.GetLink()
username, reponame, err := activitypub.RepositoryIRIToName(repoIRI)
if err != nil {
return err
}
// FederatedUserNew(username + "@" + instance, )
user, _ := user_model.GetUserByName(ctx, username)
// var repo forgefed.Repository
// repo = activity.Object
repo, _ := repo_model.GetRepositoryByOwnerAndName(actor.Name, reponame) // hardcoded for now :(
_, err = repo_service.ForkRepository(ctx, user, user, repo_service.ForkRepoOptions{BaseRepo: repo, Name: reponame, Description: "this is a remote fork"})
return err
}

View file

@ -14,13 +14,13 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/forgefed"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/activitypub"
ap "github.com/go-ap/activitypub"
)
@ -131,9 +131,9 @@ func PersonInbox(ctx *context.APIContext) {
// Process activity
switch activity.Type {
case ap.FollowType:
err = activitypub.Follow(ctx, activity)
err = follow(ctx, activity)
case ap.UndoType:
err = activitypub.Unfollow(ctx, activity)
err = unfollow(ctx, activity)
default:
log.Info("Incoming unsupported ActivityStreams type: %s", activity.GetType())
ctx.PlainText(http.StatusNotImplemented, "ActivityStreams type not supported")

View file

@ -12,13 +12,14 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/forgefed"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/services/activitypub"
pull_service "code.gitea.io/gitea/services/pull"
)
func PullRequest(ctx context.Context, ticket *forgefed.Ticket) error {
func createPullRequest(ctx context.Context, ticket *forgefed.Ticket) error {
// TODO: Clean this up
actorUser, err := PersonIRIToUser(ctx, ticket.AttributedTo.GetLink())
actorUser, err := activitypub.PersonIRIToUser(ctx, ticket.AttributedTo.GetLink())
if err != nil {
log.Warn("Couldn't find ticket actor user", err)
}

View file

@ -9,7 +9,6 @@ import (
"net/http"
"strings"
"code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/forgefed"
"code.gitea.io/gitea/modules/log"
@ -128,25 +127,25 @@ func RepoInbox(ctx *context.APIContext) {
switch o.Type {
case forgefed.RepositoryType:
// Fork created by remote instance
return activitypub.ReceiveFork(ctx, activity)
return fork(ctx, activity)
case forgefed.TicketType:
// New issue or pull request
return forgefed.OnTicket(o, func(t *forgefed.Ticket) error {
if t.Origin != nil {
// New pull request
return activitypub.PullRequest(ctx, t)
return createPullRequest(ctx, t)
}
// New issue
return activitypub.ReceiveIssue(ctx, t)
return createIssue(ctx, t)
})
case ap.NoteType:
// New comment
return activitypub.Comment(ctx, o)
return createComment(ctx, o)
}
return nil
})
case ap.LikeType:
err = activitypub.ReceiveStar(ctx, activity)
err = star(ctx, activity)
default:
log.Info("Incoming unsupported ActivityStreams type: %s", activity.Type)
ctx.PlainText(http.StatusNotImplemented, "ActivityStreams type not supported")

View file

@ -12,10 +12,9 @@ import (
"net/http"
"net/url"
"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"
"code.gitea.io/gitea/services/activitypub"
ap "github.com/go-ap/activitypub"
"github.com/go-fed/httpsig"
@ -85,7 +84,7 @@ func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, er
return
}
err = user_service.FederatedUserNew(ctx, &person)
err = createPerson(ctx, &person)
return authenticated, err
}

View file

@ -7,10 +7,10 @@ package activitypub
import (
"net/http"
"code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/forgefed"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/services/activitypub"
ap "github.com/go-ap/activitypub"
"github.com/go-ap/jsonld"

View file

@ -9,17 +9,18 @@ import (
"strings"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/services/activitypub"
ap "github.com/go-ap/activitypub"
)
// 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())
func star(ctx context.Context, like ap.Like) (err error) {
user, err := activitypub.PersonIRIToUser(ctx, like.Actor.GetLink())
if err != nil {
return
}
repo, err := RepositoryIRIToRepository(ctx, like.Object.GetLink())
repo, err := activitypub.RepositoryIRIToRepository(ctx, like.Object.GetLink())
if err != nil || strings.Contains(repo.Name, "@") || repo.IsPrivate {
return
}

View file

@ -644,6 +644,7 @@ func Routes(ctx gocontext.Context) *web.Route {
}
m.Get("/version", misc.Version)
if setting.Federation.Enabled {
m.Get("/authorize_interaction", activitypub.AuthorizeInteraction)
m.Get("/nodeinfo", misc.NodeInfo)
m.Group("/activitypub", func() {
m.Group("/user/{username}", func() {

View file

@ -1323,10 +1323,6 @@ func RegisterRoutes(m *web.Route) {
m.Get("/new", user.NewAvailable)
}, reqSignIn)
if setting.Federation.Enabled {
m.Get("/authorize_interaction", AuthorizeInteraction)
}
if setting.API.EnableSwagger {
m.Get("/swagger.v1.json", SwaggerV1Json)
}

View file

@ -0,0 +1,53 @@
// 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 (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
ap "github.com/go-ap/activitypub"
)
// 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
}
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)
}
// 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
}
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)
}

View file

@ -2,17 +2,15 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package activitypub
package repository
import (
"context"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/forgefed"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/activitypub"
"code.gitea.io/gitea/services/migrations"
repo_service "code.gitea.io/gitea/services/repository"
ap "github.com/go-ap/activitypub"
)
@ -44,36 +42,5 @@ func CreateFork(ctx context.Context, instance, username, reponame, destUsername
// repo.ForkedFrom = forgefed.RepositoryNew(ap.IRI())
create.Object = repo
return Send(user, &create)
}
func ReceiveFork(ctx context.Context, create ap.Create) error {
// TODO: Clean this up
repository := create.Object.(*forgefed.Repository)
actor, err := PersonIRIToUser(ctx, create.Actor.GetLink())
if err != nil {
return err
}
// Don't create an actual copy of the remote repo!
// https://gitea.com/xy/gitea/issues/7
// Create the fork
repoIRI := repository.GetLink()
username, reponame, err := RepositoryIRIToName(repoIRI)
if err != nil {
return err
}
// FederatedUserNew(username + "@" + instance, )
user, _ := user_model.GetUserByName(ctx, username)
// var repo forgefed.Repository
// repo = activity.Object
repo, _ := repo_model.GetRepositoryByOwnerAndName(actor.Name, reponame) // hardcoded for now :(
_, err = repo_service.ForkRepository(ctx, user, user, repo_service.ForkRepoOptions{BaseRepo: repo, Name: reponame, Description: "this is a remote fork"})
return err
return activitypub.Send(user, &create)
}

View file

@ -1,145 +0,0 @@
// 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 user
import (
"context"
"errors"
"strings"
"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"
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
}
followUser, err := user_model.GetUserByID(followID)
if err != nil {
return err
}
if followUser.LoginType == auth.Federated {
// Unfollowing 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.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"))}
err = activitypub.Send(actorUser, unfollow)
if err != nil {
return err
}
}
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 := activitypub.PersonIRIToName(person.GetLink())
if err != nil {
return err
}
exists, err := user_model.IsUserExist(ctx, 0, name)
if err != nil {
return err
}
if exists {
return nil
}
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{
Name: name,
FullName: person.Name.String(), // May not exist!!
Email: email,
LoginType: auth.Federated,
LoginName: person.GetLink().String(),
}
err = user_model.CreateUser(user)
if err != nil {
return err
}
if person.Icon != nil {
icon := person.Icon.(*ap.Image)
iconURL, err := icon.URL.GetLink().URL()
if err != nil {
return err
}
body, err := activitypub.Fetch(iconURL)
if err != nil {
return err
}
err = UploadAvatar(user, body)
if err != nil {
return err
}
}
err = user_model.SetUserSetting(user.ID, user_model.UserActivityPubPrivPem, "")
if err != nil {
return err
}
return user_model.SetUserSetting(user.ID, user_model.UserActivityPubPubPem, person.PublicKey.PublicKeyPem)
}

View file

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
@ -26,6 +27,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/activitypub"
"code.gitea.io/gitea/services/packages"
)
@ -280,3 +282,45 @@ func DeleteAvatar(u *user_model.User) error {
}
return nil
}
// 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
}
if followUser.LoginType == auth.Federated {
// Following remote user
err = activitypub.Follow(userID, followID)
if err != nil {
return
}
}
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
}
followUser, err := user_model.GetUserByID(followID)
if err != nil {
return
}
if followUser.LoginType == auth.Federated {
// Unfollowing remote user
err = activitypub.Unfollow(userID, followID)
if err != nil {
return
}
}
return user_model.UnfollowUser(userID, followID)
}

View file

@ -13,9 +13,9 @@ import (
"testing"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/services/activitypub"
ap "github.com/go-ap/activitypub"
"github.com/stretchr/testify/assert"