diff --git a/models/forgefed/activity.go b/models/forgefed/activity.go index 491a938bd5..6e315fe591 100644 --- a/models/forgefed/activity.go +++ b/models/forgefed/activity.go @@ -4,6 +4,8 @@ package forgefed import ( + "time" + "code.gitea.io/gitea/modules/validation" ap "github.com/go-ap/activitypub" ) @@ -15,22 +17,26 @@ type ForgeLike struct { ap.Activity } -func (s ForgeLike) MarshalJSON() ([]byte, error) { - return s.Activity.MarshalJSON() +func (like ForgeLike) MarshalJSON() ([]byte, error) { + return like.Activity.MarshalJSON() } -func (s *ForgeLike) UnmarshalJSON(data []byte) error { - return s.Activity.UnmarshalJSON(data) +func (like *ForgeLike) UnmarshalJSON(data []byte) error { + return like.Activity.UnmarshalJSON(data) } -func (s ForgeLike) Validate() []string { +func (like ForgeLike) IsNewer(compareTo time.Time) bool { + return like.StartTime.After(compareTo) +} + +func (like ForgeLike) Validate() []string { var result []string - result = append(result, validation.ValidateNotEmpty(string(s.Type), "type")...) - result = append(result, validation.ValidateOneOf(string(s.Type), []any{"Like"})...) - result = append(result, validation.ValidateNotEmpty(s.Actor.GetID().String(), "actor")...) - result = append(result, validation.ValidateNotEmpty(s.Object.GetID().String(), "object")...) - result = append(result, validation.ValidateNotEmpty(s.StartTime.String(), "startTime")...) - if s.StartTime.IsZero() { + result = append(result, validation.ValidateNotEmpty(string(like.Type), "type")...) + result = append(result, validation.ValidateOneOf(string(like.Type), []any{"Like"})...) + result = append(result, validation.ValidateNotEmpty(like.Actor.GetID().String(), "actor")...) + result = append(result, validation.ValidateNotEmpty(like.Object.GetID().String(), "object")...) + result = append(result, validation.ValidateNotEmpty(like.StartTime.String(), "startTime")...) + if like.StartTime.IsZero() { result = append(result, "StartTime was invalid.") } diff --git a/models/forgefed/federationinfo.go b/models/forgefed/federationinfo.go index 417feb1e19..a24898495c 100644 --- a/models/forgefed/federationinfo.go +++ b/models/forgefed/federationinfo.go @@ -4,6 +4,8 @@ package forgefed import ( + "time" + "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/validation" ) @@ -14,7 +16,7 @@ type FederationInfo struct { ID int64 `xorm:"pk autoincr"` HostFqdn string `xorm:"host_fqdn UNIQUE INDEX VARCHAR(255) NOT NULL"` NodeInfo NodeInfo `xorm:"extends NOT NULL"` - LatestActivity timeutil.TimeStamp `xorm:"NOT NULL"` + LatestActivity time.Time `xorm:"NOT NULL"` Create timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` } diff --git a/models/forgefed/federationinfo_repository.go b/models/forgefed/federationinfo_repository.go index ab13810072..50796a8d50 100644 --- a/models/forgefed/federationinfo_repository.go +++ b/models/forgefed/federationinfo_repository.go @@ -50,3 +50,11 @@ func CreateFederationInfo(ctx context.Context, info FederationInfo) error { _, err := db.GetEngine(ctx).Insert(info) return err } + +func UpdateFederationInfo(ctx context.Context, info FederationInfo) error { + if res, err := validation.IsValid(info); !res { + return fmt.Errorf("FederationInfo is not valid: %v", err) + } + _, err := db.GetEngine(ctx).ID(info.ID).Update(info) + return err +} diff --git a/models/forgefed/federationinfo_test.go b/models/forgefed/federationinfo_test.go index d40d42dc02..4028c27b8a 100644 --- a/models/forgefed/federationinfo_test.go +++ b/models/forgefed/federationinfo_test.go @@ -5,8 +5,8 @@ package forgefed import ( "testing" + "time" - "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/validation" ) @@ -16,7 +16,7 @@ func Test_FederationInfoValidation(t *testing.T) { NodeInfo: NodeInfo{ Source: "forgejo", }, - LatestActivity: timeutil.TimeStampNow(), + LatestActivity: time.Now(), } if res, err := validation.IsValid(sut); !res { t.Errorf("sut should be valid but was %q", err) @@ -25,7 +25,7 @@ func Test_FederationInfoValidation(t *testing.T) { sut = FederationInfo{ HostFqdn: "host.do.main", NodeInfo: NodeInfo{}, - LatestActivity: timeutil.TimeStampNow(), + LatestActivity: time.Now(), } if res, _ := validation.IsValid(sut); res { t.Errorf("sut should be invalid") diff --git a/routers/api/v1/activitypub/repository.go b/routers/api/v1/activitypub/repository.go index f6dd539521..d5674ebb9a 100644 --- a/routers/api/v1/activitypub/repository.go +++ b/routers/api/v1/activitypub/repository.go @@ -51,7 +51,7 @@ func Repository(ctx *context.APIContext) { repo.Name = ap.NaturalLanguageValuesNew() err := repo.Name.Set("en", ap.Content(ctx.Repo.Repository.Name)) if err != nil { - ctx.ServerError("Set Name", err) + ctx.Error(http.StatusInternalServerError, "Set Name", err) return } @@ -86,7 +86,7 @@ func RepositoryInbox(ctx *context.APIContext) { activity := web.GetForm(ctx).(*forgefed.ForgeLike) if res, err := validation.IsValid(activity); !res { - ctx.ServerError("Validate activity", err) + ctx.Error(http.StatusNotAcceptable, "RepositoryInbox: Validate activity", err) return } log.Info("RepositoryInbox: activity validated:%v", activity) @@ -96,33 +96,39 @@ func RepositoryInbox(ctx *context.APIContext) { rawActorID, err := forgefed.NewActorID(actorUri) federationInfo, err := forgefed.FindFederationInfoByHostFqdn(ctx, rawActorID.Host) if err != nil { - ctx.ServerError("Error while loading FederationInfo: %v", err) + ctx.Error(http.StatusInternalServerError, + "RepositoryInbox: Error while loading FederationInfo", err) return } if federationInfo == nil { result, err := createFederationInfo(ctx, rawActorID) if err != nil { - ctx.ServerError("Validate actorId", err) + ctx.Error(http.StatusNotAcceptable, "RepositoryInbox: Validate actorId", err) return } federationInfo = &result log.Info("RepositoryInbox: federationInfo validated: %v", federationInfo) } + if !activity.IsNewer(federationInfo.LatestActivity) { + ctx.Error(http.StatusNotAcceptable, "RepositoryInbox: Validate Activity", + fmt.Errorf("Activity already processed")) + return + } actorID, err := forgefed.NewPersonID(actorUri, string(federationInfo.NodeInfo.Source)) if err != nil { - ctx.ServerError("Validate actorId", err) + ctx.Error(http.StatusNotAcceptable, "RepositoryInbox: Validate actorId", err) return } log.Info("RepositoryInbox: actorId validated: %v", actorID) // parse objectID (repository) objectID, err := forgefed.NewRepositoryID(activity.Object.GetID().String(), string(forgefed.ForgejoSourceType)) if err != nil { - ctx.ServerError("Validate objectId", err) + ctx.Error(http.StatusNotAcceptable, "RepositoryInbox: Validate objectId", err) return } if objectID.ID != fmt.Sprint(repository.ID) { - ctx.ServerError("Validate objectId", err) + ctx.Error(http.StatusNotAcceptable, "RepositoryInbox: Validate objectId", err) return } log.Info("RepositoryInbox: objectId validated: %v", objectID) @@ -133,7 +139,7 @@ func RepositoryInbox(ctx *context.APIContext) { // Check if user already exists users, err := SearchUsersByLoginName(actorAsLoginID) if err != nil { - ctx.ServerError("Searching for user failed", err) + ctx.Error(http.StatusInternalServerError, "RepositoryInbox: Searching for user failed", err) return } log.Info("RepositoryInbox: local found users: %v", len(users)) @@ -143,7 +149,8 @@ func RepositoryInbox(ctx *context.APIContext) { { user, err = createUserFromAP(ctx, actorID) if err != nil { - ctx.ServerError("Creating user failed", err) + ctx.Error(http.StatusInternalServerError, + "RepositoryInbox: Creating federated user failed", err) return } log.Info("RepositoryInbox: created user from ap: %v", user) @@ -155,8 +162,8 @@ func RepositoryInbox(ctx *context.APIContext) { } default: { - ctx.Error(http.StatusInternalServerError, "StarRepo", - fmt.Errorf("found more than one matches for federated users")) + ctx.Error(http.StatusInternalServerError, "RepositoryInbox", + fmt.Errorf(" more than one matches for federated users")) return } } @@ -166,10 +173,16 @@ func RepositoryInbox(ctx *context.APIContext) { if !alreadyStared { err = repo_model.StarRepo(ctx, user.ID, repository.ID, true) if err != nil { - ctx.Error(http.StatusInternalServerError, "StarRepo", err) + ctx.Error(http.StatusNotAcceptable, "RepositoryInbox: Star operation", err) return } } + federationInfo.LatestActivity = activity.StartTime + err = forgefed.UpdateFederationInfo(ctx, *federationInfo) + if err != nil { + ctx.Error(http.StatusNotAcceptable, "RepositoryInbox: error updateing federateionInfo", err) + return + } ctx.Status(http.StatusNoContent) }