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:
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
|
// 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 web
|
package activitypub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"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"
|
"code.gitea.io/gitea/services/activitypub"
|
||||||
|
|
||||||
ap "github.com/go-ap/activitypub"
|
ap "github.com/go-ap/activitypub"
|
||||||
)
|
)
|
||||||
|
@ -45,7 +44,7 @@ func AuthorizeInteraction(ctx *context.Context) {
|
||||||
ctx.ServerError("UnmarshalJSON", err)
|
ctx.ServerError("UnmarshalJSON", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = user_service.FederatedUserNew(ctx, object.(*ap.Person))
|
err = createPerson(ctx, object.(*ap.Person))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("FederatedUserNew", err)
|
ctx.ServerError("FederatedUserNew", err)
|
||||||
return
|
return
|
||||||
|
@ -59,29 +58,7 @@ func AuthorizeInteraction(ctx *context.Context) {
|
||||||
case forgefed.RepositoryType:
|
case forgefed.RepositoryType:
|
||||||
// Federated repository
|
// Federated repository
|
||||||
err = forgefed.OnRepository(object, func(r *forgefed.Repository) error {
|
err = forgefed.OnRepository(object, func(r *forgefed.Repository) error {
|
||||||
ownerURL, err := url.Parse(r.AttributedTo.GetLink().String())
|
return createRepository(ctx, r)
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("FederatedRepoNew", err)
|
ctx.ServerError("FederatedRepoNew", err)
|
||||||
|
@ -117,12 +94,12 @@ func AuthorizeInteraction(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
// Create federated repo
|
// Create federated repo
|
||||||
err = forgefed.OnRepository(object, func(r *forgefed.Repository) error {
|
err = forgefed.OnRepository(object, func(r *forgefed.Repository) error {
|
||||||
return activitypub.FederatedRepoNew(ctx, r)
|
return createRepository(ctx, r)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return activitypub.ReceiveIssue(ctx, t)
|
return createIssue(ctx, t)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("ReceiveIssue", err)
|
ctx.ServerError("ReceiveIssue", err)
|
199
routers/api/v1/activitypub/create.go
Normal file
199
routers/api/v1/activitypub/create.go
Normal 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
|
||||||
|
}
|
|
@ -9,22 +9,23 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/services/activitypub"
|
||||||
|
|
||||||
ap "github.com/go-ap/activitypub"
|
ap "github.com/go-ap/activitypub"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Process a Follow activity
|
// Process an incoming Follow activity
|
||||||
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 := activitypub.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 := activitypub.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
|
||||||
|
@ -40,22 +41,27 @@ func Follow(ctx context.Context, follow ap.Follow) error {
|
||||||
accept.Actor = ap.Person{ID: objectIRI}
|
accept.Actor = ap.Person{ID: objectIRI}
|
||||||
accept.To = ap.ItemCollection{ap.IRI(actorIRI.String() + "/inbox")}
|
accept.To = ap.ItemCollection{ap.IRI(actorIRI.String() + "/inbox")}
|
||||||
accept.Object = follow
|
accept.Object = follow
|
||||||
return Send(objectUser, accept)
|
return activitypub.Send(objectUser, accept)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process a Undo follow activity
|
// Process an incoming Undo follow activity
|
||||||
func Unfollow(ctx context.Context, unfollow ap.Undo) error {
|
func unfollow(ctx context.Context, unfollow ap.Undo) error {
|
||||||
follow := unfollow.Object.(*ap.Follow)
|
// 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
|
// Actor is the user performing the undo follow
|
||||||
actorIRI := follow.Actor.GetLink()
|
actorIRI := follow.Actor.GetLink()
|
||||||
actorUser, err := PersonIRIToUser(ctx, actorIRI)
|
actorUser, err := activitypub.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 := activitypub.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
|
51
routers/api/v1/activitypub/fork.go
Normal file
51
routers/api/v1/activitypub/fork.go
Normal 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
|
||||||
|
}
|
|
@ -14,13 +14,13 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
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/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/convert"
|
"code.gitea.io/gitea/modules/convert"
|
||||||
"code.gitea.io/gitea/modules/forgefed"
|
"code.gitea.io/gitea/modules/forgefed"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
|
"code.gitea.io/gitea/services/activitypub"
|
||||||
|
|
||||||
ap "github.com/go-ap/activitypub"
|
ap "github.com/go-ap/activitypub"
|
||||||
)
|
)
|
||||||
|
@ -131,9 +131,9 @@ func PersonInbox(ctx *context.APIContext) {
|
||||||
// Process activity
|
// Process activity
|
||||||
switch activity.Type {
|
switch activity.Type {
|
||||||
case ap.FollowType:
|
case ap.FollowType:
|
||||||
err = activitypub.Follow(ctx, activity)
|
err = follow(ctx, activity)
|
||||||
case ap.UndoType:
|
case ap.UndoType:
|
||||||
err = activitypub.Unfollow(ctx, activity)
|
err = unfollow(ctx, activity)
|
||||||
default:
|
default:
|
||||||
log.Info("Incoming unsupported ActivityStreams type: %s", activity.GetType())
|
log.Info("Incoming unsupported ActivityStreams type: %s", activity.GetType())
|
||||||
ctx.PlainText(http.StatusNotImplemented, "ActivityStreams type not supported")
|
ctx.PlainText(http.StatusNotImplemented, "ActivityStreams type not supported")
|
||||||
|
|
|
@ -12,13 +12,14 @@ import (
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/forgefed"
|
"code.gitea.io/gitea/modules/forgefed"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/services/activitypub"
|
||||||
pull_service "code.gitea.io/gitea/services/pull"
|
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
|
// TODO: Clean this up
|
||||||
|
|
||||||
actorUser, err := PersonIRIToUser(ctx, ticket.AttributedTo.GetLink())
|
actorUser, err := activitypub.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)
|
||||||
}
|
}
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"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"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
@ -128,25 +127,25 @@ func RepoInbox(ctx *context.APIContext) {
|
||||||
switch o.Type {
|
switch o.Type {
|
||||||
case forgefed.RepositoryType:
|
case forgefed.RepositoryType:
|
||||||
// Fork created by remote instance
|
// Fork created by remote instance
|
||||||
return activitypub.ReceiveFork(ctx, activity)
|
return fork(ctx, activity)
|
||||||
case forgefed.TicketType:
|
case forgefed.TicketType:
|
||||||
// New issue or pull request
|
// New issue or pull request
|
||||||
return forgefed.OnTicket(o, func(t *forgefed.Ticket) error {
|
return forgefed.OnTicket(o, func(t *forgefed.Ticket) error {
|
||||||
if t.Origin != nil {
|
if t.Origin != nil {
|
||||||
// New pull request
|
// New pull request
|
||||||
return activitypub.PullRequest(ctx, t)
|
return createPullRequest(ctx, t)
|
||||||
}
|
}
|
||||||
// New issue
|
// New issue
|
||||||
return activitypub.ReceiveIssue(ctx, t)
|
return createIssue(ctx, t)
|
||||||
})
|
})
|
||||||
case ap.NoteType:
|
case ap.NoteType:
|
||||||
// New comment
|
// New comment
|
||||||
return activitypub.Comment(ctx, o)
|
return createComment(ctx, o)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
case ap.LikeType:
|
case ap.LikeType:
|
||||||
err = activitypub.ReceiveStar(ctx, activity)
|
err = star(ctx, activity)
|
||||||
default:
|
default:
|
||||||
log.Info("Incoming unsupported ActivityStreams type: %s", activity.Type)
|
log.Info("Incoming unsupported ActivityStreams type: %s", activity.Type)
|
||||||
ctx.PlainText(http.StatusNotImplemented, "ActivityStreams type not supported")
|
ctx.PlainText(http.StatusNotImplemented, "ActivityStreams type not supported")
|
||||||
|
|
|
@ -12,10 +12,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"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"
|
"code.gitea.io/gitea/services/activitypub"
|
||||||
|
|
||||||
ap "github.com/go-ap/activitypub"
|
ap "github.com/go-ap/activitypub"
|
||||||
"github.com/go-fed/httpsig"
|
"github.com/go-fed/httpsig"
|
||||||
|
@ -85,7 +84,7 @@ func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, er
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = user_service.FederatedUserNew(ctx, &person)
|
err = createPerson(ctx, &person)
|
||||||
return authenticated, err
|
return authenticated, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,10 @@ package activitypub
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"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"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/services/activitypub"
|
||||||
|
|
||||||
ap "github.com/go-ap/activitypub"
|
ap "github.com/go-ap/activitypub"
|
||||||
"github.com/go-ap/jsonld"
|
"github.com/go-ap/jsonld"
|
||||||
|
|
|
@ -9,17 +9,18 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/services/activitypub"
|
||||||
|
|
||||||
ap "github.com/go-ap/activitypub"
|
ap "github.com/go-ap/activitypub"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 star(ctx context.Context, like ap.Like) (err error) {
|
||||||
user, err := PersonIRIToUser(ctx, like.Actor.GetLink())
|
user, err := activitypub.PersonIRIToUser(ctx, like.Actor.GetLink())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
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 {
|
if err != nil || strings.Contains(repo.Name, "@") || repo.IsPrivate {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -644,6 +644,7 @@ func Routes(ctx gocontext.Context) *web.Route {
|
||||||
}
|
}
|
||||||
m.Get("/version", misc.Version)
|
m.Get("/version", misc.Version)
|
||||||
if setting.Federation.Enabled {
|
if setting.Federation.Enabled {
|
||||||
|
m.Get("/authorize_interaction", activitypub.AuthorizeInteraction)
|
||||||
m.Get("/nodeinfo", misc.NodeInfo)
|
m.Get("/nodeinfo", misc.NodeInfo)
|
||||||
m.Group("/activitypub", func() {
|
m.Group("/activitypub", func() {
|
||||||
m.Group("/user/{username}", func() {
|
m.Group("/user/{username}", func() {
|
||||||
|
|
|
@ -1323,10 +1323,6 @@ func RegisterRoutes(m *web.Route) {
|
||||||
m.Get("/new", user.NewAvailable)
|
m.Get("/new", user.NewAvailable)
|
||||||
}, reqSignIn)
|
}, reqSignIn)
|
||||||
|
|
||||||
if setting.Federation.Enabled {
|
|
||||||
m.Get("/authorize_interaction", AuthorizeInteraction)
|
|
||||||
}
|
|
||||||
|
|
||||||
if setting.API.EnableSwagger {
|
if setting.API.EnableSwagger {
|
||||||
m.Get("/swagger.v1.json", SwaggerV1Json)
|
m.Get("/swagger.v1.json", SwaggerV1Json)
|
||||||
}
|
}
|
||||||
|
|
53
services/activitypub/follow.go
Normal file
53
services/activitypub/follow.go
Normal 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)
|
||||||
|
}
|
|
@ -2,17 +2,15 @@
|
||||||
// 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 repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/forgefed"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/services/activitypub"
|
||||||
"code.gitea.io/gitea/services/migrations"
|
"code.gitea.io/gitea/services/migrations"
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
|
||||||
|
|
||||||
ap "github.com/go-ap/activitypub"
|
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())
|
// repo.ForkedFrom = forgefed.RepositoryNew(ap.IRI())
|
||||||
create.Object = repo
|
create.Object = repo
|
||||||
|
|
||||||
return Send(user, &create)
|
return activitypub.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
|
|
||||||
}
|
}
|
|
@ -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"
|
"code.gitea.io/gitea/models"
|
||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
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/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
|
@ -26,6 +27,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/services/activitypub"
|
||||||
"code.gitea.io/gitea/services/packages"
|
"code.gitea.io/gitea/services/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -280,3 +282,45 @@ func DeleteAvatar(u *user_model.User) error {
|
||||||
}
|
}
|
||||||
return nil
|
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"
|
"testing"
|
||||||
|
|
||||||
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"
|
||||||
"code.gitea.io/gitea/routers"
|
"code.gitea.io/gitea/routers"
|
||||||
|
"code.gitea.io/gitea/services/activitypub"
|
||||||
|
|
||||||
ap "github.com/go-ap/activitypub"
|
ap "github.com/go-ap/activitypub"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
Reference in a new issue