diff --git a/go.mod b/go.mod index 3fd6e06494..fd220854d4 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/ethantkoenig/rupture v1.0.1 github.com/felixge/fgprof v0.9.2 github.com/gliderlabs/ssh v0.3.4 - github.com/go-ap/activitypub v0.0.0-00010101000000-000000000000 + github.com/go-ap/activitypub v0.0.0-20220706134811-0c84d76ce535 github.com/go-ap/jsonld v0.0.0-20220615144122-1d862b15410d github.com/go-chi/chi/v5 v5.0.7 github.com/go-chi/cors v1.2.1 diff --git a/models/forgefed/repository.go b/models/forgefed/repository.go index e85e793ae8..b127dea7c1 100644 --- a/models/forgefed/repository.go +++ b/models/forgefed/repository.go @@ -19,6 +19,8 @@ type Repository struct { Team ap.Item `jsonld:"team,omitempty"` // Forks OrderedCollection of repositories that are forks of this repository Forks ap.Item `jsonld:"forks,omitempty"` + // ForkedFrom Identifies the repository which this repository was created as a fork + ForkedFrom ap.Item `jsonld:"forkedFrom,omitempty"` } // RepositoryNew initializes a Repository type actor @@ -42,6 +44,9 @@ func (r Repository) MarshalJSON() ([]byte, error) { if r.Forks != nil { ap.WriteItemJSONProp(&b, "forks", r.Forks) } + if r.ForkedFrom != nil { + ap.WriteItemJSONProp(&b, "forkedFrom", r.ForkedFrom) + } ap.Write(&b, '}') return b, nil } @@ -55,6 +60,7 @@ func (r *Repository) UnmarshalJSON(data []byte) error { r.Team = ap.JSONGetItem(val, "team") r.Forks = ap.JSONGetItem(val, "forks") + r.ForkedFrom = ap.JSONGetItem(val, "forkedFrom") return ap.OnActor(&r.Actor, func(a *ap.Actor) error { return ap.LoadActor(val, a) diff --git a/modules/activitypub/fork.go b/modules/activitypub/fork.go new file mode 100644 index 0000000000..dd67fa2899 --- /dev/null +++ b/modules/activitypub/fork.go @@ -0,0 +1,68 @@ +// 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" + "strings" + + //"code.gitea.io/gitea/models/forgefed" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/migrations" + repo_service "code.gitea.io/gitea/services/repository" + + ap "github.com/go-ap/activitypub" +) + +func Fork(ctx context.Context, instance, username, reponame, destUsername string) { + // Migrate repository code + user, _ := user_model.GetUserByName(ctx, destUsername) + _, err := migrations.MigrateRepository(ctx, user, destUsername, migrations.MigrateOptions{ + CloneAddr: "https://" + instance + "/" + username + "/" + reponame + ".git", + RepoName: reponame, + }, nil) + if err != nil { + log.Warn("Couldn't create fork", err) + } + + // Make the migrated repo a fork + + // Send a Create activity to the instance we are forking from + create := ap.Create{Type: ap.CreateType} + create.To = ap.ItemCollection{ap.IRI("https://" + instance + "/api/v1/activitypub/repo/" + username + "/" + reponame + "/inbox")} + repo := ap.IRI(strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/repo/" + destUsername + "/" + reponame) + // repo := forgefed.RepositoryNew(ap.IRI(strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/repo/" + destUsername + "/" + reponame)) + // repo.ForkedFrom = forgefed.RepositoryNew(ap.IRI()) + create.Object = repo + + Send(user, &create) +} + +func ForkFromCreate(ctx context.Context, activity ap.Create) { + // Don't create an actual copy of the remote repo! + // https://gitea.com/Ta180m/gitea/issues/7 + + // Create the fork + repoIRI := activity.Object.GetID() + repoIRISplit := strings.Split(repoIRI.String(), "/") + instance := repoIRISplit[2] + username := repoIRISplit[7] + reponame := repoIRISplit[8] + + // FederatedUserNew(username + "@" + instance, ) + user, _ := user_model.GetUserByName(ctx, username+"@"+instance) + + // var repo forgefed.Repository + // repo = activity.Object + repo, _ := repo_model.GetRepositoryByOwnerAndName("Ta180m", reponame) // hardcoded for now :( + + _, err := repo_service.ForkRepository(ctx, user, user, repo_service.ForkRepoOptions{BaseRepo: repo, Name: reponame, Description: "this is a remote fork"}) + log.Warn("Couldn't create copy of remote fork", err) + + // TODO: send back accept +} diff --git a/modules/activitypub/issue.go b/modules/activitypub/issue.go index a809dcbe89..e962d97544 100644 --- a/modules/activitypub/issue.go +++ b/modules/activitypub/issue.go @@ -6,7 +6,6 @@ package activitypub import ( "context" - "fmt" "strconv" "strings" @@ -35,15 +34,13 @@ func Comment(ctx context.Context, activity ap.Note) { contextSplit := strings.Split(context.String(), "/") username := contextSplit[3] reponame := contextSplit[4] - fmt.Println(username) - fmt.Println(reponame) repo, _ := repo_model.GetRepositoryByOwnerAndName(username, reponame) idx, _ := strconv.ParseInt(contextSplit[len(contextSplit)-1], 10, 64) issue, _ := issues.GetIssueByIndex(repo.ID, idx) issues.CreateCommentCtx(ctx, &issues.CreateCommentOptions{ - Doer: actorUser, - Repo: repo, - Issue: issue, + Doer: actorUser, + Repo: repo, + Issue: issue, Content: activity.Content.String(), }) } diff --git a/modules/activitypub/pull_request.go b/modules/activitypub/pull_request.go new file mode 100644 index 0000000000..10a3d3020f --- /dev/null +++ b/modules/activitypub/pull_request.go @@ -0,0 +1,76 @@ +// 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" + "strings" + + issues_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/log" + pull_service "code.gitea.io/gitea/services/pull" + + ap "github.com/go-ap/activitypub" +) + +func PullRequest(ctx context.Context, activity ap.Move) { + actorIRI := activity.AttributedTo.GetLink() + actorIRISplit := strings.Split(actorIRI.String(), "/") + actorName := actorIRISplit[len(actorIRISplit)-1] + "@" + actorIRISplit[2] + err := FederatedUserNew(actorName, actorIRI) + if err != nil { + log.Warn("Couldn't create new user", err) + } + actorUser, err := user_model.GetUserByName(ctx, actorName) + if err != nil { + log.Warn("Couldn't find actor", err) + } + + // This code is really messy + // The IRI processing stuff should be in a separate function + originIRI := activity.Origin.GetLink() + originIRISplit := strings.Split(originIRI.String(), "/") + originInstance := originIRISplit[2] + originUsername := originIRISplit[3] + originReponame := originIRISplit[4] + originBranch := originIRISplit[len(originIRISplit)-1] + originRepo, _ := repo_model.GetRepositoryByOwnerAndName(originUsername+"@"+originInstance, originReponame) + + targetIRI := activity.Target.GetLink() + targetIRISplit := strings.Split(targetIRI.String(), "/") + // targetInstance := targetIRISplit[2] + targetUsername := targetIRISplit[3] + targetReponame := targetIRISplit[4] + targetBranch := targetIRISplit[len(targetIRISplit)-1] + + targetRepo, _ := repo_model.GetRepositoryByOwnerAndName(targetUsername, targetReponame) + + prIssue := &issues_model.Issue{ + RepoID: targetRepo.ID, + Title: "Hello from test.exozy.me!", // Don't hardcode, get the title from the Ticket object + PosterID: actorUser.ID, + Poster: actorUser, + IsPull: true, + Content: "🎉", + } + + pr := &issues_model.PullRequest{ + HeadRepoID: originRepo.ID, + BaseRepoID: targetRepo.ID, + HeadBranch: originBranch, + HeadCommitID: "73f228996f27fad2c7bb60435f912d943b66b0ee", // hardcoded for now + BaseBranch: targetBranch, + HeadRepo: originRepo, + BaseRepo: targetRepo, + MergeBase: "", + Type: issues_model.PullRequestGitea, + } + + err = pull_service.NewPullRequest(ctx, targetRepo, prIssue, []int64{}, []string{}, pr, []int64{}) + fmt.Println(err) +} diff --git a/routers/api/v1/activitypub/repo.go b/routers/api/v1/activitypub/repo.go index 34b4bcdcbe..0f63e3e521 100644 --- a/routers/api/v1/activitypub/repo.go +++ b/routers/api/v1/activitypub/repo.go @@ -96,13 +96,21 @@ func RepoInbox(ctx *context.APIContext) { if err != nil { ctx.ServerError("Error reading request body", err) } - - var activity ap.Object - activity.UnmarshalJSON(body) + var activity ap.Activity + activity.UnmarshalJSON(body) // This function doesn't support ForgeFed types!!! log.Warn("Debug", activity) - if activity.Type == ap.NoteType { - activitypub.Comment(ctx, activity) - } else { + switch activity.Type { + case ap.NoteType: + // activitypub.Comment(ctx, activity) + case ap.CreateType: + // if activity.Object.GetType() == forgefed.RepositoryType { + // Fork created by remote instance + activitypub.ForkFromCreate(ctx, activity) + //} + case ap.MoveType: + // This should actually be forgefed.TicketType but that the UnmarshalJSON function above doesn't support ForgeFed! + activitypub.PullRequest(ctx, activity) + default: log.Warn("ActivityStreams type not supported", activity) } diff --git a/routers/api/v1/activitypub/reqsignature.go b/routers/api/v1/activitypub/reqsignature.go index ff3280d954..8f7a92fc06 100644 --- a/routers/api/v1/activitypub/reqsignature.go +++ b/routers/api/v1/activitypub/reqsignature.go @@ -65,6 +65,7 @@ func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, er return } // 3. Verify the other actor's key + // TODO: Verify attributedTo matches keyID algo := httpsig.Algorithm(setting.Federation.Algorithms[0]) authenticated = v.Verify(pubKey, algo) == nil return authenticated, err