mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-24 08:57:03 -05:00
Merge branch 'forgejo-federated-star' of codeberg.org:meissa/forgejo into forgejo-federated-star
This commit is contained in:
commit
7316108d56
9 changed files with 77 additions and 3 deletions
|
@ -6,6 +6,7 @@ package forgefed
|
|||
import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
|
@ -18,6 +19,21 @@ type ForgeLike struct {
|
|||
ap.Activity
|
||||
}
|
||||
|
||||
func NewForgeLike(ctx *context.APIContext) (ForgeLike, error) {
|
||||
result := ForgeLike{}
|
||||
actorIRI := ctx.Repo.Owner.APAPIURL()
|
||||
objectIRI := ctx.Repo.Repository.APAPIURL()
|
||||
result.Type = ap.LikeType
|
||||
// ToDo: Would validating the source by Actor.Type field make sense?
|
||||
result.Actor = ap.ActorNew(ap.IRI(actorIRI), "ForgejoUser") // Thats us, a User
|
||||
result.Object = ap.ObjectNew(ap.ActivityVocabularyType(objectIRI)) // Thats them, a Repository
|
||||
result.StartTime = time.Now()
|
||||
if valid, err := validation.IsValid(result); !valid {
|
||||
return ForgeLike{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (like ForgeLike) MarshalJSON() ([]byte, error) {
|
||||
return like.Activity.MarshalJSON()
|
||||
}
|
||||
|
|
|
@ -346,6 +346,11 @@ func (repo *Repository) APIURL() string {
|
|||
return setting.AppURL + "api/v1/repos/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
|
||||
}
|
||||
|
||||
// APAPIURL returns the activitypub repository API URL
|
||||
func (repo *Repository) APAPIURL() string {
|
||||
return setting.AppURL + "api/v1/activitypub/repository-id/" + url.PathEscape(string(repo.ID))
|
||||
}
|
||||
|
||||
// GetCommitsCountCacheKey returns cache key used for commits count caching.
|
||||
func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool) string {
|
||||
var prefix string
|
||||
|
|
|
@ -301,6 +301,11 @@ func (u *User) HTMLURL() string {
|
|||
return setting.AppURL + url.PathEscape(u.Name)
|
||||
}
|
||||
|
||||
// APAPIURL returns the IRI to the api endpoint of the user
|
||||
func (u *User) APAPIURL() string {
|
||||
return setting.AppURL + url.PathEscape("api/v1/activitypub/user-id/") + url.PathEscape(string(u.ID))
|
||||
}
|
||||
|
||||
// OrganisationLink returns the organization sub page link.
|
||||
func (u *User) OrganisationLink() string {
|
||||
return setting.AppSubURL + "/org/" + url.PathEscape(u.Name)
|
||||
|
|
|
@ -22,6 +22,14 @@ import (
|
|||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ToDo: May need to change the name to reflect workings of function better
|
||||
// LikeActivity receives a ForgeLike activity and does the following:
|
||||
// Validation of the activity
|
||||
// Creation of a (remote) federationHost if not existing
|
||||
// Creation of a forgefed Person if not existing
|
||||
// Validation of incoming RepositoryID against Local RepositoryID
|
||||
// Star the repo if it wasn't already stared
|
||||
// Do some mitigation against out of order attacks
|
||||
func LikeActivity(ctx *context.APIContext, form any, repositoryID int64) (int, string, error) {
|
||||
activity := form.(*forgefed.ForgeLike)
|
||||
if res, err := validation.IsValid(activity); !res {
|
||||
|
@ -37,7 +45,7 @@ func LikeActivity(ctx *context.APIContext, form any, repositoryID int64) (int, s
|
|||
}
|
||||
federationHost, err := forgefed.FindFederationHostByFqdn(ctx, rawActorID.Host)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, "Could not loading FederationHost", err
|
||||
return http.StatusInternalServerError, "Could not load FederationHost", err
|
||||
}
|
||||
if federationHost == nil {
|
||||
result, err := CreateFederationHostFromAP(ctx, rawActorID)
|
||||
|
|
|
@ -157,6 +157,10 @@ func IsValidFederatedRepoURLList(urls string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func IsOfValidLength(str string) bool {
|
||||
return len(str) <= 2048
|
||||
}
|
||||
|
||||
var (
|
||||
validUsernamePatternWithDots = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
|
||||
validUsernamePatternWithoutDots = regexp.MustCompile(`^[\da-zA-Z][-\w]*$`)
|
||||
|
|
|
@ -46,7 +46,7 @@ func ValidateNotEmpty(value any, fieldName string) []string {
|
|||
if isValid {
|
||||
return []string{}
|
||||
}
|
||||
return []string{fmt.Sprintf("Field %v may not be empty", fieldName)}
|
||||
return []string{fmt.Sprintf("Field %v should not be empty", fieldName)}
|
||||
}
|
||||
|
||||
func ValidateMaxLen(value string, maxLen int, fieldName string) []string {
|
||||
|
|
|
@ -898,7 +898,7 @@ func Routes() *web.Route {
|
|||
}, context_service.UserIDAssignmentAPI())
|
||||
m.Group("/repository-id/{repository-id}", func() {
|
||||
m.Get("", activitypub.Repository)
|
||||
m.Post("/inbox", // ToDo: Post or Put?
|
||||
m.Post("/inbox",
|
||||
// TODO: bind ativities here
|
||||
bind(forgefed.ForgeLike{}),
|
||||
// TODO: activitypub.ReqHTTPSignature(),
|
||||
|
|
|
@ -7,12 +7,16 @@ package user
|
|||
import (
|
||||
std_context "context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/forgefed"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
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/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
|
@ -160,6 +164,32 @@ func Star(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusInternalServerError, "StarRepo", err)
|
||||
return
|
||||
}
|
||||
if setting.Federation.Enabled {
|
||||
|
||||
likeActivity, err := forgefed.NewForgeLike(ctx)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "StarRepo", err)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := likeActivity.MarshalJSON()
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "StarRepo", err)
|
||||
return
|
||||
}
|
||||
|
||||
apclient, err := activitypub.NewClient(ctx, ctx.Doer, ctx.Doer.APAPIURL())
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "StarRepo", err)
|
||||
return
|
||||
}
|
||||
// ToDo: Change this to the standalone table of FederatedRepos
|
||||
for _, target := range strings.Split(ctx.Repo.Repository.FederationRepos, ";") {
|
||||
apclient.Post([]byte(json), target)
|
||||
}
|
||||
|
||||
// Send to list of federated repos
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
|
|
@ -192,11 +192,17 @@ func SettingsPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// ToDo: Use Federated Repo Struct & Update Federated Repo Table
|
||||
switch {
|
||||
// Allow clearing the field
|
||||
case form.FederationRepos == "":
|
||||
repo.FederationRepos = ""
|
||||
// Validate
|
||||
case !validation.IsOfValidLength(form.FederationRepos): // ToDo: Use for public testing only. In production we might need longer strings.
|
||||
ctx.Data["ERR_FederationRepos"] = true
|
||||
ctx.Flash.Error("The given string was larger than 2048 bytes")
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
return
|
||||
case validation.IsValidFederatedRepoURL(form.FederationRepos):
|
||||
repo.FederationRepos = form.FederationRepos
|
||||
default:
|
||||
|
|
Loading…
Reference in a new issue