From b2c8a1cfd357b1f1c5bbc4a2c9abf3b15bb54f67 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Mon, 28 Jul 2025 10:55:04 +0200 Subject: [PATCH] Revert "fix: assorted ActivityPub code only refactors (#8274)" (#8705) This reverts commit e271c24100c8974e135ebc1d1b3eaa6cd2062b10. It was an experiment to verify that adding a delay to the test make a difference. But it does not so... reverting. @jerger before engaging in a refactor, it is necessary to get to the bottom of this: - Find the root cause of those failures - Fix it in a minimal way Refs https://codeberg.org/forgejo/forgejo/pulls/8274#issuecomment-5987215 --- - https://codeberg.org/forgejo/forgejo/actions/runs/92182/jobs/9 - https://codeberg.org/forgejo/forgejo/actions/runs/92182/jobs/10 ``` --- FAIL: TestFederationHttpSigValidation (11.34s) testlogger.go:411: 2025/07/28 00:23:46 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /workspace/forgejo/forgejo/tests/gitea-lfs-meta testlogger.go:411: 2025/07/28 00:23:52 ...ypub/reqsignature.go:76:func1() [W] verifyHttpSignatures failed: neither "Signature" nor "Authorization" have signature parameters testlogger.go:411: 2025/07/28 00:23:52 ...eb/routing/logger.go:102:func1() [I] router: completed GET http://127.0.0.1:3002/api/v1/activitypub/user-id/2 for test-mock:12345, 400 Bad Request in 5.3ms @ activitypub/reqsignature.go:74(activitypub.ReqHTTPUserOrInstanceSignature) testlogger.go:411: 2025/07/28 00:23:52 ...ces/auth/httpsign.go:70:Verify() [W] Failed authentication attempt from 127.0.0.1:43244 testlogger.go:411: 2025/07/28 00:23:55 ...eb/routing/logger.go:68:func1() [W] router: slow GET /api/v1/activitypub/user-id/2 for 127.0.0.1:43244, elapsed 3684.7ms @ activitypub/reqsignature.go:74(activitypub.ReqHTTPUserOrInstanceSignature) --- FAIL: TestFederationHttpSigValidation/SignedRequest (5.01s) api_federation_httpsig_test.go:50: Error Trace: /workspace/forgejo/forgejo/tests/integration/api_federation_httpsig_test.go:50 Error: Received unexpected error: Get "http://127.0.0.1:3002/api/v1/activitypub/user-id/2": context deadline exceeded (Client.Timeout exceeded while awaiting headers) Test: TestFederationHttpSigValidation/SignedRequest --- FAIL: TestFederationHttpSigValidation/ValidateCaches (0.00s) api_federation_httpsig_test.go:64: Error Trace: /workspace/forgejo/forgejo/tests/integration/api_federation_httpsig_test.go:64 Error: Expected value not to be nil. Test: TestFederationHttpSigValidation/ValidateCaches test_utils.go:247: PrepareTestEnv:Process "GET: /api/v1/activitypub/user-id/2" cancelled panic: runtime error: invalid memory address or nil pointer dereference [recovered] panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4cc464a] ``` Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8705 Reviewed-by: jerger Co-authored-by: Earl Warren Co-committed-by: Earl Warren --- .deadcode-out | 3 - models/repo/repo_repository.go | 6 +- models/user/follow.go | 16 +- models/user/user_repository.go | 16 +- modules/activitypub/client.go | 7 +- routers/api/v1/activitypub/repository.go | 9 +- routers/api/v1/activitypub/reqsignature.go | 26 +-- routers/api/v1/activitypub/response.go | 34 ---- routers/api/v1/api.go | 3 +- services/federation/error.go | 44 ----- services/federation/federation_service.go | 19 +- ...{repository_inbox_like.go => repo_like.go} | 33 ++-- services/federation/repository_service.go | 19 -- services/federation/result.go | 35 ---- services/federation/signature_service.go | 11 +- .../integration/api_activitypub_actor_test.go | 77 -------- .../api_activitypub_person_test.go | 113 ----------- .../api_activitypub_repository_test.go | 176 ------------------ .../api_federation_httpsig_test.go | 82 -------- .../integration/repo_star_federation_test.go | 81 -------- 20 files changed, 69 insertions(+), 741 deletions(-) delete mode 100644 services/federation/error.go rename services/federation/{repository_inbox_like.go => repo_like.go} (73%) delete mode 100644 services/federation/repository_service.go delete mode 100644 services/federation/result.go delete mode 100644 tests/integration/api_activitypub_actor_test.go delete mode 100644 tests/integration/api_activitypub_person_test.go delete mode 100644 tests/integration/api_activitypub_repository_test.go delete mode 100644 tests/integration/api_federation_httpsig_test.go delete mode 100644 tests/integration/repo_star_federation_test.go diff --git a/.deadcode-out b/.deadcode-out index 31b04687dc..f9bee43043 100644 --- a/.deadcode-out +++ b/.deadcode-out @@ -246,10 +246,7 @@ forgejo.org/services/context GetPrivateContext forgejo.org/services/federation - NewErrInternalf - ErrInternal.Error Init - NewServiceResultWithBytes forgejo.org/services/repository IsErrForkAlreadyExist diff --git a/models/repo/repo_repository.go b/models/repo/repo_repository.go index 9d586b8345..0ba50e6614 100644 --- a/models/repo/repo_repository.go +++ b/models/repo/repo_repository.go @@ -38,18 +38,18 @@ func StoreFollowingRepos(ctx context.Context, localRepoID int64, followingRepoLi } // Begin transaction - dbCtx, committer, err := db.TxContext((ctx)) + ctx, committer, err := db.TxContext((ctx)) if err != nil { return err } defer committer.Close() - _, err = db.GetEngine(dbCtx).Where("repo_id=?", localRepoID).Delete(FollowingRepo{}) + _, err = db.GetEngine(ctx).Where("repo_id=?", localRepoID).Delete(FollowingRepo{}) if err != nil { return err } for _, followingRepo := range followingRepoList { - _, err = db.GetEngine(dbCtx).Insert(followingRepo) + _, err = db.GetEngine(ctx).Insert(followingRepo) if err != nil { return err } diff --git a/models/user/follow.go b/models/user/follow.go index 8663b2a943..e32c226385 100644 --- a/models/user/follow.go +++ b/models/user/follow.go @@ -39,21 +39,21 @@ func FollowUser(ctx context.Context, userID, followID int64) (err error) { return ErrBlockedByUser } - dbCtx, committer, err := db.TxContext(ctx) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } defer committer.Close() - if err = db.Insert(dbCtx, &Follow{UserID: userID, FollowID: followID}); err != nil { + if err = db.Insert(ctx, &Follow{UserID: userID, FollowID: followID}); err != nil { return err } - if _, err = db.Exec(dbCtx, "UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", followID); err != nil { + if _, err = db.Exec(ctx, "UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", followID); err != nil { return err } - if _, err = db.Exec(dbCtx, "UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", userID); err != nil { + if _, err = db.Exec(ctx, "UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", userID); err != nil { return err } return committer.Commit() @@ -65,21 +65,21 @@ func UnfollowUser(ctx context.Context, userID, followID int64) (err error) { return nil } - dbCtx, committer, err := db.TxContext(ctx) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } defer committer.Close() - if _, err = db.DeleteByBean(dbCtx, &Follow{UserID: userID, FollowID: followID}); err != nil { + if _, err = db.DeleteByBean(ctx, &Follow{UserID: userID, FollowID: followID}); err != nil { return err } - if _, err = db.Exec(dbCtx, "UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil { + if _, err = db.Exec(ctx, "UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil { return err } - if _, err = db.Exec(dbCtx, "UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil { + if _, err = db.Exec(ctx, "UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil { return err } return committer.Commit() diff --git a/models/user/user_repository.go b/models/user/user_repository.go index df864746e8..85f44f1598 100644 --- a/models/user/user_repository.go +++ b/models/user/user_repository.go @@ -28,7 +28,7 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat } // Begin transaction - txCtx, committer, err := db.TxContext(ctx) + ctx, committer, err := db.TxContext((ctx)) if err != nil { return err } @@ -39,7 +39,7 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat } }() - if err := CreateUser(txCtx, user, &overwrite); err != nil { + if err := CreateUser(ctx, user, &overwrite); err != nil { return err } @@ -48,7 +48,7 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat return err } - _, err = db.GetEngine(txCtx).Insert(federatedUser) + _, err = db.GetEngine(ctx).Insert(federatedUser) if err != nil { return err } @@ -70,7 +70,7 @@ func FindFederatedUser(ctx context.Context, externalID string, federationHostID if err != nil { return nil, nil, err } else if !has { - return nil, nil, fmt.Errorf("FederatedUser table contains entry for user ID %v, but no user with this ID exists", federatedUser.UserID) + return nil, nil, fmt.Errorf("User %v for federated user is missing", federatedUser.UserID) } if res, err := validation.IsValid(*user); !res { @@ -87,7 +87,7 @@ func GetFederatedUser(ctx context.Context, externalID string, federationHostID i if err != nil { return nil, nil, err } else if federatedUser == nil { - return nil, nil, fmt.Errorf("FederatedUser not found (given externalId: %v, federationHostId: %v)", externalID, federationHostID) + return nil, nil, fmt.Errorf("FederatedUser for externalId = %v and federationHostId = %v does not exist", externalID, federationHostID) } return user, federatedUser, nil } @@ -99,13 +99,13 @@ func GetFederatedUserByUserID(ctx context.Context, userID int64) (*User, *Federa if err != nil { return nil, nil, err } else if !has { - return nil, nil, fmt.Errorf("FederatedUser table does not contain entry for user ID: %v", federatedUser.UserID) + return nil, nil, fmt.Errorf("Federated user %v does not exist", federatedUser.UserID) } has, err = db.GetEngine(ctx).ID(federatedUser.UserID).Get(user) if err != nil { return nil, nil, err } else if !has { - return nil, nil, fmt.Errorf("FederatedUser table contains entry for user ID %v, but no user with this ID exists", federatedUser.UserID) + return nil, nil, fmt.Errorf("User %v for federated user is missing", federatedUser.UserID) } if res, err := validation.IsValid(*user); !res { @@ -130,7 +130,7 @@ func FindFederatedUserByKeyID(ctx context.Context, keyID string) (*User, *Federa if err != nil { return nil, nil, err } else if !has { - return nil, nil, fmt.Errorf("FederatedUser table contains entry for user ID %v, but no user with this ID exists", federatedUser.UserID) + return nil, nil, fmt.Errorf("User %v for federated user is missing", federatedUser.UserID) } if res, err := validation.IsValid(*user); !res { diff --git a/modules/activitypub/client.go b/modules/activitypub/client.go index 11a2fd94c3..fb6fa8b543 100644 --- a/modules/activitypub/client.go +++ b/modules/activitypub/client.go @@ -66,11 +66,6 @@ type ClientFactory struct { // NewClient function func NewClientFactory() (c *ClientFactory, err error) { - return NewClientFactoryWithTimeout(5 * time.Second) -} - -// NewClient function -func NewClientFactoryWithTimeout(timeout time.Duration) (c *ClientFactory, err error) { if err = containsRequiredHTTPHeaders(http.MethodGet, setting.Federation.GetHeaders); err != nil { return nil, err } else if err = containsRequiredHTTPHeaders(http.MethodPost, setting.Federation.PostHeaders); err != nil { @@ -82,7 +77,7 @@ func NewClientFactoryWithTimeout(timeout time.Duration) (c *ClientFactory, err e Transport: &http.Transport{ Proxy: proxy.Proxy(), }, - Timeout: timeout, + Timeout: 5 * time.Second, }, algs: setting.HttpsigAlgs, digestAlg: httpsig.DigestAlgorithm(setting.Federation.DigestAlgorithm), diff --git a/routers/api/v1/activitypub/repository.go b/routers/api/v1/activitypub/repository.go index 3eaa6b82c5..c506840f1c 100644 --- a/routers/api/v1/activitypub/repository.go +++ b/routers/api/v1/activitypub/repository.go @@ -71,11 +71,10 @@ func RepositoryInbox(ctx *context.APIContext) { repository := ctx.Repo.Repository log.Info("RepositoryInbox: repo: %v", repository) form := web.GetForm(ctx) - activity := form.(*ap.Activity) - result, err := federation.ProcessRepositoryInbox(ctx, activity, repository.ID) + // TODO: Decide between like/undo{like} activity + httpStatus, title, err := federation.ProcessLikeActivity(ctx, form, repository.ID) if err != nil { - ctx.Error(federation.HTTPStatus(err), "Processing Repository Inbox failed", result) - return + ctx.Error(httpStatus, title, err) } - responseServiceResult(ctx, result) + ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/activitypub/reqsignature.go b/routers/api/v1/activitypub/reqsignature.go index 38cb067b89..91274249ec 100644 --- a/routers/api/v1/activitypub/reqsignature.go +++ b/routers/api/v1/activitypub/reqsignature.go @@ -8,13 +8,13 @@ import ( "forgejo.org/modules/log" "forgejo.org/modules/setting" - services_context "forgejo.org/services/context" + gitea_context "forgejo.org/services/context" "forgejo.org/services/federation" "github.com/42wim/httpsig" ) -func verifyHTTPUserOrInstanceSignature(ctx services_context.APIContext) (authenticated bool, err error) { +func verifyHTTPUserOrInstanceSignature(ctx *gitea_context.APIContext) (authenticated bool, err error) { if !setting.Federation.SignatureEnforced { return true, nil } @@ -28,9 +28,9 @@ func verifyHTTPUserOrInstanceSignature(ctx services_context.APIContext) (authent } signatureAlgorithm := httpsig.Algorithm(setting.Federation.SignatureAlgorithms[0]) - pubKey, err := federation.FindOrCreateFederatedUserKey(ctx, v.KeyId()) + pubKey, err := federation.FindOrCreateFederatedUserKey(ctx.Base, v.KeyId()) if err != nil || pubKey == nil { - pubKey, err = federation.FindOrCreateFederationHostKey(ctx, v.KeyId()) + pubKey, err = federation.FindOrCreateFederationHostKey(ctx.Base, v.KeyId()) if err != nil { return false, err } @@ -43,7 +43,7 @@ func verifyHTTPUserOrInstanceSignature(ctx services_context.APIContext) (authent return true, nil } -func verifyHTTPUserSignature(ctx services_context.APIContext) (authenticated bool, err error) { +func verifyHTTPUserSignature(ctx *gitea_context.APIContext) (authenticated bool, err error) { if !setting.Federation.SignatureEnforced { return true, nil } @@ -57,7 +57,7 @@ func verifyHTTPUserSignature(ctx services_context.APIContext) (authenticated boo } signatureAlgorithm := httpsig.Algorithm(setting.Federation.SignatureAlgorithms[0]) - pubKey, err := federation.FindOrCreateFederatedUserKey(ctx, v.KeyId()) + pubKey, err := federation.FindOrCreateFederatedUserKey(ctx.Base, v.KeyId()) if err != nil { return false, err } @@ -70,9 +70,9 @@ func verifyHTTPUserSignature(ctx services_context.APIContext) (authenticated boo } // ReqHTTPSignature function -func ReqHTTPUserOrInstanceSignature() func(ctx *services_context.APIContext) { - return func(ctx *services_context.APIContext) { - if authenticated, err := verifyHTTPUserOrInstanceSignature(*ctx); err != nil { +func ReqHTTPUserOrInstanceSignature() func(ctx *gitea_context.APIContext) { + return func(ctx *gitea_context.APIContext) { + if authenticated, err := verifyHTTPUserOrInstanceSignature(ctx); err != nil { log.Warn("verifyHttpSignatures failed: %v", err) ctx.Error(http.StatusBadRequest, "reqSignature", "request signature verification failed") } else if !authenticated { @@ -81,10 +81,10 @@ func ReqHTTPUserOrInstanceSignature() func(ctx *services_context.APIContext) { } } -// ReqHTTPUserSignature function -func ReqHTTPUserSignature() func(ctx *services_context.APIContext) { - return func(ctx *services_context.APIContext) { - if authenticated, err := verifyHTTPUserSignature(*ctx); err != nil { +// ReqHTTPSignature function +func ReqHTTPUserSignature() func(ctx *gitea_context.APIContext) { + return func(ctx *gitea_context.APIContext) { + if authenticated, err := verifyHTTPUserSignature(ctx); err != nil { log.Warn("verifyHttpSignatures failed: %v", err) ctx.Error(http.StatusBadRequest, "reqSignature", "request signature verification failed") } else if !authenticated { diff --git a/routers/api/v1/activitypub/response.go b/routers/api/v1/activitypub/response.go index 64413cebb1..a97f363cc2 100644 --- a/routers/api/v1/activitypub/response.go +++ b/routers/api/v1/activitypub/response.go @@ -10,46 +10,12 @@ import ( "forgejo.org/modules/forgefed" "forgejo.org/modules/log" "forgejo.org/services/context" - "forgejo.org/services/federation" ap "github.com/go-ap/activitypub" "github.com/go-ap/jsonld" ) // Respond with an ActivityStreams object -func responseServiceResult(ctx *context.APIContext, result federation.ServiceResult) { - ctx.Resp.Header().Add("Content-Type", activitypub.ActivityStreamsContentType) - - switch { - case result.StatusOnly(): - ctx.Resp.WriteHeader(result.HTTPStatus) - return - case result.WithBytes(): - ctx.Resp.WriteHeader(result.HTTPStatus) - if _, err := ctx.Resp.Write(result.Bytes); err != nil { - log.Error("Error writing a response: %v", err) - ctx.Error(http.StatusInternalServerError, "Error writing a response", err) - return - } - case result.WithActivity(): - binary, err := jsonld.WithContext( - jsonld.IRI(ap.ActivityBaseURI), - jsonld.IRI(ap.SecurityContextURI), - jsonld.IRI(forgefed.ForgeFedNamespaceURI), - ).Marshal(result.Activity) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - ctx.Resp.WriteHeader(result.HTTPStatus) - if _, err = ctx.Resp.Write(binary); err != nil { - log.Error("write to resp err: %v", err) - } - } -} - -// Respond with an ActivityStreams object -// Deprecated func response(ctx *context.APIContext, v any) { binary, err := jsonld.WithContext( jsonld.IRI(ap.ActivityBaseURI), diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 2f806ba35d..6a51f33bd8 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -81,6 +81,7 @@ import ( repo_model "forgejo.org/models/repo" "forgejo.org/models/unit" user_model "forgejo.org/models/user" + "forgejo.org/modules/forgefed" "forgejo.org/modules/log" "forgejo.org/modules/setting" api "forgejo.org/modules/structs" @@ -852,7 +853,7 @@ func Routes() *web.Route { m.Group("/repository-id/{repository-id}", func() { m.Get("", activitypub.ReqHTTPUserSignature(), activitypub.Repository) m.Post("/inbox", - bind(ap.Activity{}), + bind(forgefed.ForgeLike{}), activitypub.ReqHTTPUserSignature(), activitypub.RepositoryInbox) }, context.RepositoryIDAssignmentAPI()) diff --git a/services/federation/error.go b/services/federation/error.go deleted file mode 100644 index 425035d0d5..0000000000 --- a/services/federation/error.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2025 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package federation - -import ( - "fmt" - "net/http" -) - -type ErrNotAcceptable struct { - Message string -} - -func NewErrNotAcceptablef(format string, a ...any) ErrNotAcceptable { - message := fmt.Sprintf(format, a...) - return ErrNotAcceptable{Message: message} -} - -func (err ErrNotAcceptable) Error() string { - return fmt.Sprintf("NotAcceptable: %v", err.Message) -} - -type ErrInternal struct { - Message string -} - -func NewErrInternalf(format string, a ...any) ErrInternal { - message := fmt.Sprintf(format, a...) - return ErrInternal{Message: message} -} - -func (err ErrInternal) Error() string { - return fmt.Sprintf("InternalServerError: %v", err.Message) -} - -func HTTPStatus(err error) int { - switch err.(type) { - case ErrNotAcceptable: - return http.StatusNotAcceptable - default: - return http.StatusInternalServerError - } -} diff --git a/services/federation/federation_service.go b/services/federation/federation_service.go index 36788e725a..b71d8d2575 100644 --- a/services/federation/federation_service.go +++ b/services/federation/federation_service.go @@ -18,6 +18,7 @@ import ( "forgejo.org/modules/log" "forgejo.org/modules/setting" "forgejo.org/modules/validation" + context_service "forgejo.org/services/context" "github.com/google/uuid" ) @@ -26,7 +27,7 @@ func Init() error { return nil } -func FindOrCreateFederationHost(ctx context.Context, actorURI string) (*forgefed.FederationHost, error) { +func FindOrCreateFederationHost(ctx *context_service.Base, actorURI string) (*forgefed.FederationHost, error) { rawActorID, err := fm.NewActorID(actorURI) if err != nil { return nil, err @@ -45,7 +46,7 @@ func FindOrCreateFederationHost(ctx context.Context, actorURI string) (*forgefed return federationHost, nil } -func FindOrCreateFederatedUser(ctx context.Context, actorURI string) (*user.User, *user.FederatedUser, *forgefed.FederationHost, error) { +func FindOrCreateFederatedUser(ctx *context_service.Base, actorURI string) (*user.User, *user.FederatedUser, *forgefed.FederationHost, error) { user, federatedUser, federationHost, err := findFederatedUser(ctx, actorURI) if err != nil { return nil, nil, nil, err @@ -56,21 +57,20 @@ func FindOrCreateFederatedUser(ctx context.Context, actorURI string) (*user.User } if user != nil { - log.Trace("Local ActivityPub user found (actorURI: %#v, user: %#v)", actorURI, user) + log.Trace("Found local federatedUser: %#v", user) } else { - log.Trace("Attempting to create new user and federatedUser for actorURI: %#v", actorURI) user, federatedUser, err = createUserFromAP(ctx, personID, federationHost.ID) if err != nil { return nil, nil, nil, err } - log.Trace("Created user %#v with federatedUser %#v from distant server", user, federatedUser) + log.Trace("Created federatedUser from ap: %#v", user) } log.Trace("Got user: %v", user.Name) return user, federatedUser, federationHost, nil } -func findFederatedUser(ctx context.Context, actorURI string) (*user.User, *user.FederatedUser, *forgefed.FederationHost, error) { +func findFederatedUser(ctx *context_service.Base, actorURI string) (*user.User, *user.FederatedUser, *forgefed.FederationHost, error) { federationHost, err := FindOrCreateFederationHost(ctx, actorURI) if err != nil { return nil, nil, nil, err @@ -90,7 +90,6 @@ func findFederatedUser(ctx context.Context, actorURI string) (*user.User, *user. func createFederationHostFromAP(ctx context.Context, actorID fm.ActorID) (*forgefed.FederationHost, error) { actionsUser := user.NewAPServerActor() - clientFactory, err := activitypub.GetClientFactory(ctx) if err != nil { return nil, err @@ -162,7 +161,7 @@ func fetchUserFromAP(ctx context.Context, personID fm.PersonID, federationHostID return nil, nil, err } - log.Info("Fetched valid person from distant server: %q", person) + log.Info("Fetched valid person:%q", person) localFqdn, err := url.ParseRequestURI(setting.AppURL) if err != nil { @@ -221,7 +220,7 @@ func fetchUserFromAP(ctx context.Context, personID fm.PersonID, federationHostID }, } - log.Info("Fetched person's %q federatedUser from distant server: %q", person, federatedUser) + log.Info("Fetch federatedUser:%q", federatedUser) return &newUser, &federatedUser, nil } @@ -235,6 +234,6 @@ func createUserFromAP(ctx context.Context, personID fm.PersonID, federationHostI return nil, nil, err } - log.Info("Created federatedUser: %q", federatedUser) + log.Info("Created federatedUser:%q", federatedUser) return newUser, federatedUser, nil } diff --git a/services/federation/repository_inbox_like.go b/services/federation/repo_like.go similarity index 73% rename from services/federation/repository_inbox_like.go rename to services/federation/repo_like.go index 478a12d92c..c1e6500c61 100644 --- a/services/federation/repository_inbox_like.go +++ b/services/federation/repo_like.go @@ -5,6 +5,7 @@ package federation import ( "context" + "errors" "fmt" "net/http" "time" @@ -17,8 +18,6 @@ import ( "forgejo.org/modules/log" "forgejo.org/modules/validation" context_service "forgejo.org/services/context" - - ap "github.com/go-ap/activitypub" ) // ProcessLikeActivity receives a ForgeLike activity and does the following: @@ -28,32 +27,32 @@ 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 ProcessLikeActivity(ctx context.Context, activity *ap.Activity, repositoryID int64) (ServiceResult, error) { - constructorLikeActivity, _ := fm.NewForgeLike(activity.Actor.GetLink().String(), activity.Object.GetLink().String(), activity.StartTime) - if res, err := validation.IsValid(constructorLikeActivity); !res { - return ServiceResult{}, NewErrNotAcceptablef("Invalid activity: %v", err) +func ProcessLikeActivity(ctx *context_service.APIContext, form any, repositoryID int64) (int, string, error) { + activity := form.(*fm.ForgeLike) + if res, err := validation.IsValid(activity); !res { + return http.StatusNotAcceptable, "Invalid activity", err } log.Trace("Activity validated: %#v", activity) // parse actorID (person) actorURI := activity.Actor.GetID().String() - user, _, federationHost, err := FindOrCreateFederatedUser(ctx, actorURI) + user, _, federationHost, err := FindOrCreateFederatedUser(ctx.Base, actorURI) if err != nil { - log.Error("Federated user not found (%s): %v", actorURI, err) - return ServiceResult{}, NewErrNotAcceptablef("FindOrCreateFederatedUser failed: %v", err) + ctx.Error(http.StatusNotAcceptable, "Federated user not found", err) + return http.StatusInternalServerError, "FindOrCreateFederatedUser", err } - if !constructorLikeActivity.IsNewer(federationHost.LatestActivity) { - return ServiceResult{}, NewErrNotAcceptablef("LatestActivity: activity already processed: %v", err) + if !activity.IsNewer(federationHost.LatestActivity) { + return http.StatusNotAcceptable, "Activity out of order.", errors.New("Activity already processed") } // parse objectID (repository) - objectID, err := fm.NewRepositoryID(constructorLikeActivity.Object.GetID().String(), string(forgefed.ForgejoSourceType)) + objectID, err := fm.NewRepositoryID(activity.Object.GetID().String(), string(forgefed.ForgejoSourceType)) if err != nil { - return ServiceResult{}, NewErrNotAcceptablef("Parsing repo objectID failed: %v", err) + return http.StatusNotAcceptable, "Invalid objectId", err } if objectID.ID != fmt.Sprint(repositoryID) { - return ServiceResult{}, NewErrNotAcceptablef("Invalid repoId: %v", err) + return http.StatusNotAcceptable, "Invalid objectId", err } log.Trace("Object accepted: %#v", objectID) @@ -62,16 +61,16 @@ func ProcessLikeActivity(ctx context.Context, activity *ap.Activity, repositoryI if !alreadyStared { err = repo.StarRepo(ctx, user.ID, repositoryID, true) if err != nil { - return ServiceResult{}, NewErrNotAcceptablef("Staring failed: %v", err) + return http.StatusNotAcceptable, "Error staring", err } } federationHost.LatestActivity = activity.StartTime err = forgefed.UpdateFederationHost(ctx, federationHost) if err != nil { - return ServiceResult{}, NewErrNotAcceptablef("Updating federatedHost failed: %v", err) + return http.StatusNotAcceptable, "Error updating federatedHost", err } - return NewServiceResultStatusOnly(http.StatusNoContent), nil + return 0, "", nil } // Create or update a list of FollowingRepo structs diff --git a/services/federation/repository_service.go b/services/federation/repository_service.go deleted file mode 100644 index 7891d786e2..0000000000 --- a/services/federation/repository_service.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2025 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package federation - -import ( - "context" - - ap "github.com/go-ap/activitypub" -) - -func ProcessRepositoryInbox(ctx context.Context, activity *ap.Activity, repositoryID int64) (ServiceResult, error) { - switch activity.Type { - case ap.LikeType: - return ProcessLikeActivity(ctx, activity, repositoryID) - default: - return ServiceResult{}, NewErrNotAcceptablef("Not a like activity: %v", activity.Type) - } -} diff --git a/services/federation/result.go b/services/federation/result.go deleted file mode 100644 index 47afb2bdf6..0000000000 --- a/services/federation/result.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2025 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package federation - -import "github.com/go-ap/activitypub" - -type ServiceResult struct { - HTTPStatus int - Bytes []byte - Activity activitypub.Activity - withBytes bool - withActivity bool - statusOnly bool -} - -func NewServiceResultStatusOnly(status int) ServiceResult { - return ServiceResult{HTTPStatus: status, statusOnly: true} -} - -func NewServiceResultWithBytes(status int, bytes []byte) ServiceResult { - return ServiceResult{HTTPStatus: status, Bytes: bytes, withBytes: true} -} - -func (serviceResult ServiceResult) WithBytes() bool { - return serviceResult.withBytes -} - -func (serviceResult ServiceResult) WithActivity() bool { - return serviceResult.withActivity -} - -func (serviceResult ServiceResult) StatusOnly() bool { - return serviceResult.statusOnly -} diff --git a/services/federation/signature_service.go b/services/federation/signature_service.go index fd8cbb39cd..e5102b89d8 100644 --- a/services/federation/signature_service.go +++ b/services/federation/signature_service.go @@ -4,7 +4,6 @@ package federation import ( - "context" "crypto/x509" "database/sql" "encoding/pem" @@ -16,12 +15,13 @@ import ( "forgejo.org/models/user" "forgejo.org/modules/activitypub" fm "forgejo.org/modules/forgefed" + context_service "forgejo.org/services/context" ap "github.com/go-ap/activitypub" ) // Factory function for ActorID. Created struct is asserted to be valid -func NewActorIDFromKeyID(ctx context.Context, uri string) (fm.ActorID, error) { +func NewActorIDFromKeyID(ctx *context_service.Base, uri string) (fm.ActorID, error) { parsedURI, err := url.Parse(uri) parsedURI.Fragment = "" if err != nil { @@ -54,7 +54,7 @@ func NewActorIDFromKeyID(ctx context.Context, uri string) (fm.ActorID, error) { return result, err } -func FindOrCreateFederatedUserKey(ctx context.Context, keyID string) (pubKey any, err error) { +func FindOrCreateFederatedUserKey(ctx *context_service.Base, keyID string) (pubKey any, err error) { var federatedUser *user.FederatedUser var keyURL *url.URL @@ -122,7 +122,7 @@ func FindOrCreateFederatedUserKey(ctx context.Context, keyID string) (pubKey any return nil, nil } -func FindOrCreateFederationHostKey(ctx context.Context, keyID string) (pubKey any, err error) { +func FindOrCreateFederationHostKey(ctx *context_service.Base, keyID string) (pubKey any, err error) { keyURL, err := url.Parse(keyID) if err != nil { return nil, err @@ -183,9 +183,8 @@ func FindOrCreateFederationHostKey(ctx context.Context, keyID string) (pubKey an return nil, nil } -func fetchKeyFromAp(ctx context.Context, keyURL url.URL) (pubKey any, pubKeyBytes []byte, apPerson *ap.Person, err error) { +func fetchKeyFromAp(ctx *context_service.Base, keyURL url.URL) (pubKey any, pubKeyBytes []byte, apPerson *ap.Person, err error) { actionsUser := user.NewAPServerActor() - clientFactory, err := activitypub.GetClientFactory(ctx) if err != nil { return nil, nil, nil, err diff --git a/tests/integration/api_activitypub_actor_test.go b/tests/integration/api_activitypub_actor_test.go deleted file mode 100644 index 42232bd640..0000000000 --- a/tests/integration/api_activitypub_actor_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2024 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "fmt" - "net/http" - "net/url" - "strconv" - "testing" - - "forgejo.org/modules/forgefed" - "forgejo.org/modules/setting" - "forgejo.org/modules/test" - "forgejo.org/routers" - "forgejo.org/services/contexttest" - "forgejo.org/services/federation" - "forgejo.org/tests" - - ap "github.com/go-ap/activitypub" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestActivityPubActor(t *testing.T) { - defer test.MockVariableValue(&setting.Federation.Enabled, true)() - defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() - defer tests.PrepareTestEnv(t)() - - req := NewRequest(t, "GET", "/api/v1/activitypub/actor") - resp := MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "@context") - - var actor ap.Actor - err := actor.UnmarshalJSON(resp.Body.Bytes()) - require.NoError(t, err) - - assert.Equal(t, ap.ApplicationType, actor.Type) - assert.Equal(t, "ghost", actor.PreferredUsername.String()) - keyID := actor.GetID().String() - assert.Regexp(t, "activitypub/actor$", keyID) - assert.Regexp(t, "activitypub/actor/inbox$", actor.Inbox.GetID().String()) - - pubKey := actor.PublicKey - assert.NotNil(t, pubKey) - publicKeyID := keyID + "#main-key" - assert.Equal(t, pubKey.ID.String(), publicKeyID) - - pubKeyPem := pubKey.PublicKeyPem - assert.NotNil(t, pubKeyPem) - assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----", pubKeyPem) -} - -func TestActorNewFromKeyId(t *testing.T) { - defer test.MockVariableValue(&setting.Federation.Enabled, true)() - defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() - - onGiteaRun(t, func(t *testing.T, u *url.URL) { - ctx, _ := contexttest.MockAPIContext(t, "/api/v1/activitypub/actor") - sut, err := federation.NewActorIDFromKeyID(ctx.Base, fmt.Sprintf("%sapi/v1/activitypub/actor#main-key", u)) - require.NoError(t, err) - - port, err := strconv.ParseUint(u.Port(), 10, 16) - require.NoError(t, err) - - assert.Equal(t, forgefed.ActorID{ - ID: "actor", - HostSchema: "http", - Path: "api/v1/activitypub", - Host: setting.Domain, - HostPort: uint16(port), - UnvalidatedInput: fmt.Sprintf("http://%s:%d/api/v1/activitypub/actor", setting.Domain, port), - IsPortSupplemented: false, - }, sut) - }) -} diff --git a/tests/integration/api_activitypub_person_test.go b/tests/integration/api_activitypub_person_test.go deleted file mode 100644 index 277b150a1e..0000000000 --- a/tests/integration/api_activitypub_person_test.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "fmt" - "net/http" - "net/url" - "testing" - "time" - - "forgejo.org/models/unittest" - user_model "forgejo.org/models/user" - "forgejo.org/modules/activitypub" - "forgejo.org/modules/setting" - "forgejo.org/modules/test" - "forgejo.org/routers" - "forgejo.org/services/contexttest" - "forgejo.org/tests" - - ap "github.com/go-ap/activitypub" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestActivityPubPerson(t *testing.T) { - defer test.MockVariableValue(&setting.Federation.Enabled, true)() - defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() - - mock := test.NewFederationServerMock() - federatedSrv := mock.DistantServer(t) - defer federatedSrv.Close() - - onGiteaRun(t, func(t *testing.T, localUrl *url.URL) { - defer test.MockVariableValue(&setting.AppURL, localUrl.String())() - - localUserID := 2 - localUserName := "user2" - localUserURL := fmt.Sprintf("%sapi/v1/activitypub/user-id/%d", localUrl, localUserID) - - // distantURL := federatedSrv.URL - // distantUser15URL := fmt.Sprintf("%s/api/v1/activitypub/user-id/15", distantURL) - - // Unsigned request - t.Run("UnsignedRequest", func(t *testing.T) { - req := NewRequest(t, "GET", localUserURL) - MakeRequest(t, req, http.StatusBadRequest) - }) - - // Signed request - t.Run("SignedRequestValidation", func(t *testing.T) { - ctx, _ := contexttest.MockAPIContext(t, localUserURL) - cf, err := activitypub.NewClientFactoryWithTimeout(60 * time.Second) - require.NoError(t, err) - - c, err := cf.WithKeysDirect(ctx, mock.Persons[0].PrivKey, - mock.Persons[0].KeyID(federatedSrv.URL)) - require.NoError(t, err) - - resp, err := c.GetBody(localUserURL) - require.NoError(t, err) - - var person ap.Person - err = person.UnmarshalJSON(resp) - require.NoError(t, err) - - assert.Equal(t, ap.PersonType, person.Type) - assert.Equal(t, localUserName, person.PreferredUsername.String()) - assert.Regexp(t, fmt.Sprintf("activitypub/user-id/%d$", localUserID), person.GetID()) - assert.Regexp(t, fmt.Sprintf("activitypub/user-id/%d/inbox$", localUserID), person.Inbox.GetID().String()) - - assert.NotNil(t, person.PublicKey) - assert.Regexp(t, fmt.Sprintf("activitypub/user-id/%d#main-key$", localUserID), person.PublicKey.ID) - - assert.NotNil(t, person.PublicKey.PublicKeyPem) - assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----", person.PublicKey.PublicKeyPem) - }) - }) -} - -func TestActivityPubMissingPerson(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.Federation.Enabled, true)() - defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() - - req := NewRequest(t, "GET", "/api/v1/activitypub/user-id/999999999") - resp := MakeRequest(t, req, http.StatusNotFound) - assert.Contains(t, resp.Body.String(), "user does not exist") -} - -func TestActivityPubPersonInbox(t *testing.T) { - defer test.MockVariableValue(&setting.Federation.Enabled, true)() - defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() - - onGiteaRun(t, func(t *testing.T, u *url.URL) { - defer test.MockVariableValue(&setting.AppURL, u.String())() - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - - user1url := u.JoinPath("/api/v1/activitypub/user-id/1").String() + "#main-key" - user2inboxurl := u.JoinPath("/api/v1/activitypub/user-id/2/inbox").String() - ctx, _ := contexttest.MockAPIContext(t, user2inboxurl) - cf, err := activitypub.NewClientFactoryWithTimeout(60 * time.Second) - require.NoError(t, err) - c, err := cf.WithKeys(ctx, user1, user1url) - require.NoError(t, err) - - // Signed request "succeeds" - resp, err := c.Post([]byte{}, user2inboxurl) - require.NoError(t, err) - assert.Equal(t, http.StatusNoContent, resp.StatusCode) - }) -} diff --git a/tests/integration/api_activitypub_repository_test.go b/tests/integration/api_activitypub_repository_test.go deleted file mode 100644 index b4be0407b9..0000000000 --- a/tests/integration/api_activitypub_repository_test.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2024, 2025 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "fmt" - "net/http" - "net/url" - "testing" - "time" - - "forgejo.org/models/forgefed" - "forgejo.org/models/unittest" - "forgejo.org/models/user" - "forgejo.org/modules/activitypub" - forgefed_modules "forgejo.org/modules/forgefed" - "forgejo.org/modules/setting" - "forgejo.org/modules/test" - "forgejo.org/routers" - "forgejo.org/services/contexttest" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestActivityPubRepository(t *testing.T) { - defer test.MockVariableValue(&setting.Federation.Enabled, true)() - defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() - - mock := test.NewFederationServerMock() - federatedSrv := mock.DistantServer(t) - defer federatedSrv.Close() - - onGiteaRun(t, func(t *testing.T, u *url.URL) { - repositoryID := 2 - - localRepository := fmt.Sprintf("%sapi/v1/activitypub/repository-id/%d", u, repositoryID) - - ctx, _ := contexttest.MockAPIContext(t, localRepository) - cf, err := activitypub.NewClientFactoryWithTimeout(60 * time.Second) - require.NoError(t, err) - - c, err := cf.WithKeysDirect(ctx, mock.Persons[0].PrivKey, - mock.Persons[0].KeyID(federatedSrv.URL)) - require.NoError(t, err) - - resp, err := c.GetBody(localRepository) - require.NoError(t, err) - assert.Contains(t, string(resp), "@context") - - var repository forgefed_modules.Repository - err = repository.UnmarshalJSON(resp) - require.NoError(t, err) - - assert.Regexp(t, fmt.Sprintf("activitypub/repository-id/%d$", repositoryID), repository.GetID().String()) - }) -} - -func TestActivityPubMissingRepository(t *testing.T) { - defer test.MockVariableValue(&setting.Federation.Enabled, true)() - defer test.MockVariableValue(&setting.Federation.SignatureEnforced, false)() - defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() - - repositoryID := 9999999 - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/activitypub/repository-id/%d", repositoryID)) - resp := MakeRequest(t, req, http.StatusNotFound) - assert.Contains(t, resp.Body.String(), "repository does not exist") -} - -func TestActivityPubRepositoryInboxValid(t *testing.T) { - defer test.MockVariableValue(&setting.Federation.Enabled, true)() - defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() - - mock := test.NewFederationServerMock() - federatedSrv := mock.DistantServer(t) - defer federatedSrv.Close() - - onGiteaRun(t, func(t *testing.T, u *url.URL) { - repositoryID := 2 - timeNow := time.Now().UTC() - localRepoInbox := u.JoinPath(fmt.Sprintf("/api/v1/activitypub/repository-id/%d/inbox", repositoryID)).String() - - ctx, _ := contexttest.MockAPIContext(t, localRepoInbox) - cf, err := activitypub.NewClientFactoryWithTimeout(60 * time.Second) - require.NoError(t, err) - - c, err := cf.WithKeysDirect(ctx, mock.Persons[0].PrivKey, - mock.Persons[0].KeyID(federatedSrv.URL)) - require.NoError(t, err) - - activity1 := []byte(fmt.Sprintf( - `{"type":"Like",`+ - `"startTime":"%s",`+ - `"actor":"%s/api/v1/activitypub/user-id/15",`+ - `"object":"%s"}`, - timeNow.Format(time.RFC3339), - federatedSrv.URL, u.JoinPath(fmt.Sprintf("/api/v1/activitypub/repository-id/%d", repositoryID)).String())) - t.Logf("activity: %s", activity1) - resp, err := c.Post(activity1, localRepoInbox) - - require.NoError(t, err) - assert.Equal(t, http.StatusNoContent, resp.StatusCode) - - federationHost := unittest.AssertExistsAndLoadBean(t, &forgefed.FederationHost{HostFqdn: "127.0.0.1"}) - federatedUser := unittest.AssertExistsAndLoadBean(t, &user.FederatedUser{ExternalID: "15", FederationHostID: federationHost.ID}) - unittest.AssertExistsAndLoadBean(t, &user.User{ID: federatedUser.UserID}) - - // A like activity by a different user of the same federated host. - activity2 := []byte(fmt.Sprintf( - `{"type":"Like",`+ - `"startTime":"%s",`+ - `"actor":"%s/api/v1/activitypub/user-id/30",`+ - `"object":"%s"}`, - // Make sure this activity happens later then the one before - timeNow.Add(time.Second).Format(time.RFC3339), - federatedSrv.URL, u.JoinPath(fmt.Sprintf("/api/v1/activitypub/repository-id/%d", repositoryID)).String())) - t.Logf("activity: %s", activity2) - resp, err = c.Post(activity2, localRepoInbox) - - require.NoError(t, err) - assert.Equal(t, http.StatusNoContent, resp.StatusCode) - - federatedUser = unittest.AssertExistsAndLoadBean(t, &user.FederatedUser{ExternalID: "30", FederationHostID: federationHost.ID}) - unittest.AssertExistsAndLoadBean(t, &user.User{ID: federatedUser.UserID}) - - // The same user sends another like activity - otherRepositoryID := 3 - otherRepoInboxURL := u.JoinPath(fmt.Sprintf("/api/v1/activitypub/repository-id/%d/inbox", otherRepositoryID)).String() - activity3 := []byte(fmt.Sprintf( - `{"type":"Like",`+ - `"startTime":"%s",`+ - `"actor":"%s/api/v1/activitypub/user-id/30",`+ - `"object":"%s"}`, - // Make sure this activity happens later then the ones before - timeNow.Add(time.Second*2).Format(time.RFC3339), - federatedSrv.URL, u.JoinPath(fmt.Sprintf("/api/v1/activitypub/repository-id/%d", otherRepositoryID)).String())) - t.Logf("activity: %s", activity3) - resp, err = c.Post(activity3, otherRepoInboxURL) - - require.NoError(t, err) - assert.Equal(t, http.StatusNoContent, resp.StatusCode) - - federatedUser = unittest.AssertExistsAndLoadBean(t, &user.FederatedUser{ExternalID: "30", FederationHostID: federationHost.ID}) - unittest.AssertExistsAndLoadBean(t, &user.User{ID: federatedUser.UserID}) - - // Replay activity2. - resp, err = c.Post(activity2, localRepoInbox) - require.NoError(t, err) - assert.Equal(t, http.StatusNotAcceptable, resp.StatusCode) - }) -} - -func TestActivityPubRepositoryInboxInvalid(t *testing.T) { - defer test.MockVariableValue(&setting.Federation.Enabled, true)() - defer test.MockVariableValue(&setting.Federation.SignatureEnforced, false)() - defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() - - onGiteaRun(t, func(t *testing.T, u *url.URL) { - apServerActor := user.NewAPServerActor() - repositoryID := 2 - localRepo2Inbox := u.JoinPath(fmt.Sprintf("/api/v1/activitypub/repository-id/%d/inbox", repositoryID)).String() - - ctx, _ := contexttest.MockAPIContext(t, localRepo2Inbox) - cf, err := activitypub.NewClientFactoryWithTimeout(60 * time.Second) - require.NoError(t, err) - - c, err := cf.WithKeys(ctx, apServerActor, apServerActor.KeyID()) - require.NoError(t, err) - - activity := []byte(`{"type":"Wrong"}`) - resp, err := c.Post(activity, localRepo2Inbox) - require.NoError(t, err) - assert.Equal(t, http.StatusNotAcceptable, resp.StatusCode) - }) -} diff --git a/tests/integration/api_federation_httpsig_test.go b/tests/integration/api_federation_httpsig_test.go deleted file mode 100644 index a8deaa315f..0000000000 --- a/tests/integration/api_federation_httpsig_test.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2025 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "fmt" - "net/http" - "net/url" - "testing" - - "forgejo.org/models/db" - "forgejo.org/models/forgefed" - "forgejo.org/models/unittest" - "forgejo.org/models/user" - "forgejo.org/modules/activitypub" - "forgejo.org/modules/setting" - "forgejo.org/modules/test" - "forgejo.org/routers" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestFederationHttpSigValidation(t *testing.T) { - defer test.MockVariableValue(&setting.Federation.Enabled, true)() - defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() - - onGiteaRun(t, func(t *testing.T, u *url.URL) { - userID := 2 - userURL := fmt.Sprintf("%sapi/v1/activitypub/user-id/%d", u, userID) - - user1 := unittest.AssertExistsAndLoadBean(t, &user.User{ID: 1}) - - clientFactory, err := activitypub.GetClientFactory(db.DefaultContext) - require.NoError(t, err) - - apClient, err := clientFactory.WithKeys(db.DefaultContext, user1, user1.KeyID()) - require.NoError(t, err) - - // Unsigned request - t.Run("UnsignedRequest", func(t *testing.T) { - req := NewRequest(t, "GET", userURL) - MakeRequest(t, req, http.StatusBadRequest) - }) - - // Signed request - t.Run("SignedRequest", func(t *testing.T) { - resp, err := apClient.Get(userURL) - require.NoError(t, err) - assert.Equal(t, http.StatusOK, resp.StatusCode) - }) - - // HACK HACK HACK: the host part of the URL gets set to which IP forgejo is - // listening on, NOT localhost, which is the Domain given to forgejo which - // is then used for eg. the keyID all requests - applicationKeyID := fmt.Sprintf("%sapi/v1/activitypub/actor#main-key", setting.AppURL) - actorKeyID := fmt.Sprintf("%sapi/v1/activitypub/user-id/1#main-key", setting.AppURL) - - // Check for cached public keys - t.Run("ValidateCaches", func(t *testing.T) { - host, err := forgefed.FindFederationHostByKeyID(db.DefaultContext, applicationKeyID) - require.NoError(t, err) - assert.NotNil(t, host) - assert.True(t, host.PublicKey.Valid) - - _, user, err := user.FindFederatedUserByKeyID(db.DefaultContext, actorKeyID) - require.NoError(t, err) - assert.NotNil(t, user) - assert.True(t, user.PublicKey.Valid) - }) - - // Disable signature validation - defer test.MockVariableValue(&setting.Federation.SignatureEnforced, false)() - - // Unsigned request - t.Run("SignatureValidationDisabled", func(t *testing.T) { - req := NewRequest(t, "GET", userURL) - MakeRequest(t, req, http.StatusOK) - }) - }) -} diff --git a/tests/integration/repo_star_federation_test.go b/tests/integration/repo_star_federation_test.go deleted file mode 100644 index ae9a4e9f83..0000000000 --- a/tests/integration/repo_star_federation_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "fmt" - "net/http" - "strings" - "testing" - - "forgejo.org/models/forgefed" - repo_model "forgejo.org/models/repo" - "forgejo.org/models/unittest" - user_model "forgejo.org/models/user" - fm "forgejo.org/modules/forgefed" - "forgejo.org/modules/setting" - "forgejo.org/modules/test" - "forgejo.org/modules/validation" - "forgejo.org/tests" -) - -func TestActivityPubRepoFollowing(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.Federation.Enabled, true)() - - mock := test.NewFederationServerMock() - federatedSrv := mock.DistantServer(t) - defer federatedSrv.Close() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1, OwnerID: user.ID}) - session := loginUser(t, user.Name) - - t.Run("Add a following repo", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - link := fmt.Sprintf("/%s/settings", repo.FullName()) - - req := NewRequestWithValues(t, "POST", link, map[string]string{ - "_csrf": GetCSRF(t, session, link), - "action": "federation", - "following_repos": fmt.Sprintf("%s/api/v1/activitypub/repository-id/1", federatedSrv.URL), - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - // Verify it was added. - federationHost := unittest.AssertExistsAndLoadBean(t, &forgefed.FederationHost{HostFqdn: "127.0.0.1"}) - unittest.AssertExistsAndLoadBean(t, &repo_model.FollowingRepo{ - ExternalID: "1", - FederationHostID: federationHost.ID, - }) - }) - - t.Run("Star a repo having a following repo", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - repoLink := fmt.Sprintf("/%s", repo.FullName()) - link := fmt.Sprintf("%s/action/star", repoLink) - req := NewRequestWithValues(t, "POST", link, map[string]string{ - "_csrf": GetCSRF(t, session, repoLink), - }) - - session.MakeRequest(t, req, http.StatusOK) - - // Verify distant server received a like activity - like := fm.ForgeLike{} - err := like.UnmarshalJSON([]byte(mock.LastPost)) - if err != nil { - t.Errorf("Error unmarshalling ForgeLike: %q", err) - } - if isValid, err := validation.IsValid(like); !isValid { - t.Errorf("ForgeLike is not valid: %q", err) - } - activityType := like.Type - object := like.Object.GetLink().String() - isLikeType := activityType == "Like" - isCorrectObject := strings.HasSuffix(object, "/api/v1/activitypub/repository-id/1") - if !isLikeType || !isCorrectObject { - t.Error("Activity is not a like for this repo") - } - }) -}