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.forgejo-federation
parent
41e9a10763
commit
fd4d0e730e
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue