Federated starring and code cleanup

This commit is contained in:
Anthony Wang 2022-12-29 23:13:48 +00:00
parent af572f7b2b
commit cc4901ccec
No known key found for this signature in database
GPG Key ID: 42A5B952E6DD8D38
8 changed files with 114 additions and 41 deletions

View File

@ -16,7 +16,6 @@ import (
"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"
@ -117,21 +116,19 @@ func PersonInbox(ctx *context.APIContext) {
// Make sure keyID matches the user doing the activity
_, keyID, _ := getKeyID(ctx.Req)
if activity.Actor != nil && !strings.HasPrefix(keyID, activity.Actor.GetLink().String()) {
ctx.ServerError("Actor does not match HTTP signature keyID", nil)
err = checkActivityAndKeyID(activity, keyID)
if err != nil {
ctx.ServerError("keyID does not match activity", err)
return
}
if activity.AttributedTo != nil && !strings.HasPrefix(keyID, activity.AttributedTo.GetLink().String()) {
ctx.ServerError("AttributedTo does not match HTTP signature keyID", nil)
return
}
// TODO: Check activity.Object actor and attributedTo
// Process activity
switch activity.Type {
case ap.FollowType:
// Following a user
err = follow(ctx, activity)
case ap.UndoType:
// Unfollowing a user
err = unfollow(ctx, activity)
case ap.CreateType:
// TODO: this is kinda a hack
@ -142,9 +139,7 @@ func PersonInbox(ctx *context.APIContext) {
return createComment(ctx, n)
})
default:
log.Info("Incoming unsupported ActivityStreams type: %s", activity.GetType())
ctx.PlainText(http.StatusNotImplemented, "ActivityStreams type not supported")
return
err = fmt.Errorf("unsupported ActivityStreams activity type: %s", activity.GetType())
}
if err != nil {
ctx.ServerError("Could not process activity", err)

View File

@ -5,13 +5,13 @@
package activitypub
import (
"fmt"
"io"
"net/http"
"strings"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/forgefed"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
ap "github.com/go-ap/activitypub"
)
@ -87,7 +87,7 @@ func RepoInbox(ctx *context.APIContext) {
// "204":
// "$ref": "#/responses/empty"
body, err := io.ReadAll(ctx.Req.Body)
body, err := io.ReadAll(io.LimitReader(ctx.Req.Body, setting.Federation.MaxSize))
if err != nil {
ctx.ServerError("Error reading request body", err)
return
@ -105,17 +105,9 @@ func RepoInbox(ctx *context.APIContext) {
// Make sure keyID matches the user doing the activity
_, keyID, _ := getKeyID(ctx.Req)
if activity.Actor != nil && !strings.HasPrefix(keyID, activity.Actor.GetLink().String()) {
ctx.ServerError("Actor does not match HTTP signature keyID", nil)
return
}
if activity.AttributedTo != nil && !strings.HasPrefix(keyID, activity.AttributedTo.GetLink().String()) {
ctx.ServerError("AttributedTo does not match HTTP signature keyID", nil)
return
}
if activity.Object == nil {
ctx.ServerError("Activity does not contain object", err)
err = checkActivityAndKeyID(activity, keyID)
if err != nil {
ctx.ServerError("keyID does not match activity", err)
return
}
@ -139,18 +131,20 @@ func RepoInbox(ctx *context.APIContext) {
return createComment(ctx, n)
})
default:
log.Info("Incoming unsupported ActivityStreams object type: %s", activity.Object.GetType())
ctx.PlainText(http.StatusNotImplemented, "ActivityStreams object type not supported")
return
err = fmt.Errorf("unsupported ActivityStreams object type: %s", activity.Object.GetType())
}
case ap.LikeType:
// Starring a repo
err = star(ctx, activity)
case ap.UndoType:
// Unstarring a repo
err = unstar(ctx, activity)
default:
ctx.PlainText(http.StatusNotImplemented, "ActivityStreams type not supported")
return
err = fmt.Errorf("unsupported ActivityStreams activity type: %s", activity.GetType())
}
if err != nil {
ctx.ServerError("Error when processing", err)
ctx.ServerError("Could not process activity", err)
return
}
ctx.Status(http.StatusNoContent)

View File

@ -7,6 +7,7 @@ import (
"crypto"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"net/http"
"net/url"
@ -99,3 +100,25 @@ func ReqHTTPSignature() func(ctx *gitea_context.APIContext) {
}
}
}
// Check if the keyID matches the activity to prevent impersonation
func checkActivityAndKeyID(activity ap.Activity, keyID string) error {
if activity.Actor != nil && keyID != activity.Actor.GetLink().String() + "#main-key" {
return errors.New("actor does not match HTTP signature keyID")
}
if activity.AttributedTo != nil && keyID != activity.AttributedTo.GetLink().String() + "#main-key" {
return errors.New("attributedTo does not match HTTP signature keyID")
}
if activity.Object == nil {
return errors.New("activity does not contain object")
}
return ap.OnActivity(activity.Object, func(a *ap.Activity) error {
if a.Actor != nil && keyID != a.Actor.GetLink().String() + "#main-key" {
return errors.New("actor does not match HTTP signature keyID")
}
if a.AttributedTo != nil && keyID != a.AttributedTo.GetLink().String() + "#main-key" {
return errors.New("attributedTo does not match HTTP signature keyID")
}
return nil
})
}

View File

@ -26,3 +26,20 @@ func star(ctx context.Context, like ap.Like) (err error) {
}
return repo_model.StarRepo(user.ID, repo.ID, true)
}
// Process an Undo Like activity to unstar a repository
func unstar(ctx context.Context, unlike ap.Undo) (err error) {
like, err := ap.To[ap.Like](unlike.Object)
if err != nil {
return err
}
user, err := activitypub.PersonIRIToUser(ctx, like.Actor.GetLink())
if err != nil {
return
}
repo, err := activitypub.RepositoryIRIToRepository(ctx, like.Object.GetLink())
if err != nil || strings.Contains(repo.Name, "@") || repo.IsPrivate {
return
}
return repo_model.StarRepo(user.ID, repo.ID, false)
}

View File

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
repo_service "code.gitea.io/gitea/services/repository"
)
// getStarredRepos returns the repos that the user with the specified userID has
@ -151,7 +152,7 @@ func Star(ctx *context.APIContext) {
// "204":
// "$ref": "#/responses/empty"
err := repo_model.StarRepo(ctx.Doer.ID, ctx.Repo.Repository.ID, true)
err := repo_service.StarRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, true)
if err != nil {
ctx.Error(http.StatusInternalServerError, "StarRepo", err)
return
@ -179,7 +180,7 @@ func Unstar(ctx *context.APIContext) {
// "204":
// "$ref": "#/responses/empty"
err := repo_model.StarRepo(ctx.Doer.ID, ctx.Repo.Repository.ID, false)
err := repo_service.StarRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, false)
if err != nil {
ctx.Error(http.StatusInternalServerError, "StarRepo", err)
return

View File

@ -287,9 +287,9 @@ func Action(ctx *context.Context) {
case "unwatch":
err = repo_model.WatchRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, false)
case "star":
err = repo_model.StarRepo(ctx.Doer.ID, ctx.Repo.Repository.ID, true)
err = repo_service.StarRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, true)
case "unstar":
err = repo_model.StarRepo(ctx.Doer.ID, ctx.Repo.Repository.ID, false)
err = repo_service.StarRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, false)
case "accept_transfer":
err = acceptOrRejectRepoTransfer(ctx, true)
case "reject_transfer":

View File

@ -5,28 +5,38 @@
package activitypub
import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/forgefed"
ap "github.com/go-ap/activitypub"
)
// Create Follow activity
func Follow(actorUser, followUser *user_model.User) *ap.Follow {
func Follow(actorUser, followUser *user_model.User) (follow *ap.Follow) {
object := ap.PersonNew(ap.IRI(followUser.LoginName))
follow := ap.FollowNew("", object)
follow = ap.FollowNew("", object)
follow.Type = ap.FollowType
follow.Actor = ap.PersonNew(ap.IRI(actorUser.GetIRI()))
follow.To = ap.ItemCollection{ap.Item(ap.IRI(followUser.LoginName + "/inbox"))}
return follow
return
}
// Create Undo Follow activity
func Unfollow(actorUser, followUser *user_model.User) *ap.Undo {
func Unfollow(actorUser, followUser *user_model.User) (unfollow *ap.Undo) {
object := ap.PersonNew(ap.IRI(followUser.LoginName))
follow := ap.FollowNew("", object)
follow.Actor = ap.PersonNew(ap.IRI(actorUser.GetIRI()))
unfollow := ap.UndoNew("", follow)
unfollow = ap.UndoNew("", follow)
unfollow.Type = ap.UndoType
unfollow.To = ap.ItemCollection{ap.Item(ap.IRI(followUser.LoginName + "/inbox"))}
return unfollow
return
}
// Create Like activity
func Star(user *user_model.User, repo *repo_model.Repository) (like *ap.Like) {
like = ap.LikeNew("", forgefed.RepositoryNew(ap.IRI(repo.GetIRI())))
like.Actor = ap.PersonNew(ap.IRI(user.GetIRI()))
like.To = ap.ItemCollection{ap.IRI(repo.GetIRI() + "/inbox")}
return
}

View File

@ -0,0 +1,33 @@
// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
import (
"context"
"strings"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/services/activitypub"
)
// StarRepo or unstar repository.
func StarRepo(ctx context.Context, userID, repoID int64, star bool) error {
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
if err != nil {
return err
}
if strings.Contains(repo.Name, "@") {
// Federated repo
user, err := user_model.GetUserByID(ctx, userID)
if err != nil {
return err
}
err = activitypub.Send(user, activitypub.Star(user, repo))
if err != nil {
return err
}
}
return repo_model.StarRepo(userID, repoID, star)
}