diff --git a/models/repo/repo_repository.go b/models/repo/repo_repository.go index 7f0fba7c5b..5111cfaf22 100644 --- a/models/repo/repo_repository.go +++ b/models/repo/repo_repository.go @@ -16,6 +16,7 @@ func init() { db.RegisterModel(new(FederatedRepo)) } +// TODO: do we need this? func GetFederatedRepo(ctx context.Context, ID int64) (*FederatedRepo, error) { repo := new(FederatedRepo) has, err := db.GetEngine(ctx).Where("id=?", ID).Get(repo) @@ -30,6 +31,7 @@ func GetFederatedRepo(ctx context.Context, ID int64) (*FederatedRepo, error) { return repo, nil } +// TODO: do we need this? func FindFederatedRepoByFQDN(ctx context.Context, fqdn string) (*FederatedRepo, error) { repo := new(FederatedRepo) has, err := db.GetEngine(ctx).Where("host_fqdn=?", strings.ToLower(fqdn)).Get(repo) @@ -44,6 +46,7 @@ func FindFederatedRepoByFQDN(ctx context.Context, fqdn string) (*FederatedRepo, return repo, nil } +// TODO: do we need this? func CreateFederatedRepo(ctx context.Context, repo *FederatedRepo) error { if res, err := validation.IsValid(repo); !res { return fmt.Errorf("FederationInfo is not valid: %v", err) @@ -52,10 +55,31 @@ func CreateFederatedRepo(ctx context.Context, repo *FederatedRepo) error { return err } -func UpdateFederatedRepo(ctx context.Context, repo *FederatedRepo) error { - if res, err := validation.IsValid(repo); !res { - return fmt.Errorf("FederationInfo is not valid: %v", err) +func UpdateFederatedRepo(ctx context.Context, localRepoId int64, federatedRepoList []*FederatedRepo) error { + for _, federatedRepo := range federatedRepoList { + if res, err := validation.IsValid(federatedRepo); !res { + return fmt.Errorf("FederationInfo is not valid: %v", err) + } } - _, err := db.GetEngine(ctx).ID(repo.ID).Update(repo) - return err + + // Begin transaction + ctx, committer, err := db.TxContext((ctx)) + if err != nil { + return err + } + defer committer.Close() + + _, err = db.GetEngine(ctx).Where("repo_id=?", localRepoId).Delete(FederatedRepo{}) + if err != nil { + return err + } + for _, federatedRepo := range federatedRepoList { + _, err = db.GetEngine(ctx).Insert(federatedRepo) + if err != nil { + return err + } + } + + // Commit transaction + return committer.Commit() } diff --git a/modules/forgefed/federation_service.go b/modules/forgefed/federation_service.go index 23fb98c2a1..64c0eef9ae 100644 --- a/modules/forgefed/federation_service.go +++ b/modules/forgefed/federation_service.go @@ -30,7 +30,7 @@ import ( // 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) { +func LikeActivity(ctx *context.Context, form any, repositoryID int64) (int, string, error) { activity := form.(*forgefed.ForgeLike) if res, err := validation.IsValid(activity); !res { return http.StatusNotAcceptable, "Invalid activity", err @@ -39,20 +39,9 @@ func LikeActivity(ctx *context.APIContext, form any, repositoryID int64) (int, s // parse actorID (person) actorURI := activity.Actor.GetID().String() - rawActorID, err := forgefed.NewActorID(actorURI) + federationHost, err := GetFederationHostForUri(ctx, actorURI) if err != nil { - return http.StatusInternalServerError, "Invalid ActorID", err - } - federationHost, err := forgefed.FindFederationHostByFqdn(ctx, rawActorID.Host) - if err != nil { - return http.StatusInternalServerError, "Could not load FederationHost", err - } - if federationHost == nil { - result, err := CreateFederationHostFromAP(ctx, rawActorID) - if err != nil { - return http.StatusNotAcceptable, "Invalid FederationHost", err - } - federationHost = result + return http.StatusInternalServerError, "Wrong FederationHost", err } if !activity.IsNewer(federationHost.LatestActivity) { return http.StatusNotAcceptable, "Activity out of order.", fmt.Errorf("Activity already processed") @@ -106,7 +95,7 @@ func LikeActivity(ctx *context.APIContext, form any, repositoryID int64) (int, s return 0, "", nil } -func CreateFederationHostFromAP(ctx *context.APIContext, actorID forgefed.ActorID) (*forgefed.FederationHost, error) { +func CreateFederationHostFromAP(ctx *context.Context, actorID forgefed.ActorID) (*forgefed.FederationHost, error) { actionsUser := user.NewActionsUser() client, err := activitypub.NewClient(ctx, actionsUser, "no idea where to get key material.") if err != nil { @@ -139,7 +128,27 @@ func CreateFederationHostFromAP(ctx *context.APIContext, actorID forgefed.ActorI return &result, nil } -func CreateUserFromAP(ctx *context.APIContext, personID forgefed.PersonID, federationHostID int64) (*user.User, *user.FederatedUser, error) { +func GetFederationHostForUri(ctx *context.Context, actorURI string) (*forgefed.FederationHost, error) { + // parse actorID (person) + rawActorID, err := forgefed.NewActorID(actorURI) + if err != nil { + return nil, err + } + federationHost, err := forgefed.FindFederationHostByFqdn(ctx, rawActorID.Host) + if err != nil { + return nil, err + } + if federationHost == nil { + result, err := CreateFederationHostFromAP(ctx, rawActorID) + if err != nil { + return nil, err + } + federationHost = result + } + return federationHost, nil +} + +func CreateUserFromAP(ctx *context.Context, personID forgefed.PersonID, federationHostID int64) (*user.User, *user.FederatedUser, error) { // ToDo: Do we get a publicKeyId from server, repo or owner or repo? actionsUser := user.NewActionsUser() client, err := activitypub.NewClient(ctx, actionsUser, "no idea where to get key material.") @@ -202,34 +211,26 @@ func CreateUserFromAP(ctx *context.APIContext, personID forgefed.PersonID, feder return &newUser, &federatedUser, nil } -func CreateFederatedRepo(ctx *context.APIContext, federatedRepoID forgefed.RepositoryID, federationHostID int64) (repo.FederatedRepo, error) { - // ToDo - // Note: We may want to discuss about side effects here -} - -// Create a list of FederatedRepo structs -func CreateFederadedRepoList(ctx *context.APIContext, repoList []string, localRepoId int64) ([]repo.FederatedRepo, error) { - - federatedRepos := make([]repo.FederatedRepo, len(repoList)) - for _, uri := range repoList { - - federatedRepoID, err := forgefed.NewRepositoryID(uri, "forgejo") // ToDo: Don't hardcode this but where do we get this from +// Create or update a list of FederatedRepo structs +func UpdateFederatedRepoList(ctx *context.Context, localRepoId int64, federatedRepoList []string) (int, string, error) { + federatedRepos := make([]*repo.FederatedRepo, len(federatedRepoList)) + for _, uri := range federatedRepoList { + federationHost, err := GetFederationHostForUri(ctx, uri) if err != nil { - return make([]repo.FederatedRepo, 0), err + return http.StatusInternalServerError, "Wrong FederationHost", err } - - federationHost, err := forgefed.FindFederationHostByFqdn(ctx, federatedRepoID.Host) + federatedRepoID, err := forgefed.NewRepositoryID(uri, string(federationHost.NodeInfo.Source)) if err != nil { - return make([]repo.FederatedRepo, 0), err + return http.StatusNotAcceptable, "Invalid federated repo", err } - - federatedRepo, err := CreateFederatedRepo(ctx, federatedRepoID, federationHost.ID) + federatedRepo, err := repo.NewFederatedRepo(localRepoId, federatedRepoID.ID, federationHost.ID) if err != nil { - return make([]repo.FederatedRepo, 0), err + return http.StatusNotAcceptable, "Invalid federated repo", err } - - federatedRepos = append(federatedRepos, federatedRepo) + federatedRepos = append(federatedRepos, &federatedRepo) } - return federatedRepos, nil + repo.UpdateFederatedRepo(ctx, localRepoId, federatedRepos) + + return 0, "", nil } diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 640379d956..f7e6568cc3 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -20,6 +20,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/forgefed" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/indexer/stats" @@ -192,33 +193,17 @@ func SettingsPost(ctx *context.Context) { return } - // Create Federated Repo Structs & commit to DB - // ToDo: Implement in federation_service.go - // ToDo: validation.ValidateMaxLen() seems not to be suited for this case. We may need to approach differently here + federationRepos := form.FederationRepos - // ToDo: Use Federated Repo Struct & Update Federated Repo Table - // TODO: move as much functions to some kind of service in order to keep controller clean an simple - 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. + errs := validation.ValidateMaxLen(federationRepos, 2048, "federationRepos") + if len(errs) > 0 { 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): - // TODO: Move this validation to Domain!! - repo.FederationRepos = form.FederationRepos - default: - ctx.Data["ERR_FederationRepos"] = true - ctx.Flash.Error("The given Repo URL was not valid") - ctx.Redirect(repo.Link() + "/settings") - return } - if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { + if _, _, err := forgefed.UpdateFederatedRepoList(ctx, ctx.Repo.Repository.ID, strings.Split(federationRepos, ";")); err != nil { ctx.ServerError("UpdateRepository", err) return }