diff options
Diffstat (limited to 'internal/federation/federatingdb')
18 files changed, 423 insertions, 399 deletions
diff --git a/internal/federation/federatingdb/accept.go b/internal/federation/federatingdb/accept.go index 477c5e8b9..3bd84849d 100644 --- a/internal/federation/federatingdb/accept.go +++ b/internal/federation/federatingdb/accept.go @@ -20,11 +20,9 @@ package federatingdb import ( "context" - "encoding/json" "errors" "fmt" - "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/ap" @@ -37,43 +35,29 @@ import ( func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsAccept) error { l := f.log.WithFields( logrus.Fields{ - "func": "Accept", - "asType": accept.GetTypeName(), + "func": "Accept", }, ) - m, err := streams.Serialize(accept) - if err != nil { - return err + + if l.Level >= logrus.DebugLevel { + i, err := marshalItem(accept) + if err != nil { + return err + } + l = l.WithField("accept", i) + l.Debug("entering Accept") } - b, err := json.Marshal(m) + + targetAcct, fromFederatorChan, err := extractFromCtx(ctx) if err != nil { return err } - l.Debugf("received ACCEPT asType %s", string(b)) - - targetAcctI := ctx.Value(util.APAccount) - if targetAcctI == nil { - // If the target account wasn't set on the context, that means this request didn't pass through the - // API, but came from inside GtS as the result of another activity on this instance. That being so, + if targetAcct == nil || fromFederatorChan == nil { + // If the target account or federator channel wasn't set on the context, that means this request didn't pass + // through the API, but came from inside GtS as the result of another activity on this instance. That being so, // we can safely just ignore this activity, since we know we've already processed it elsewhere. return nil } - targetAcct, ok := targetAcctI.(*gtsmodel.Account) - if !ok { - l.Error("ACCEPT: target account was set on context but couldn't be parsed") - return nil - } - - fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey) - if fromFederatorChanI == nil { - l.Error("ACCEPT: from federator channel wasn't set on context") - return nil - } - fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator) - if !ok { - l.Error("ACCEPT: from federator channel was set on context but couldn't be parsed") - return nil - } acceptObject := accept.GetActivityStreamsObject() if acceptObject == nil { diff --git a/internal/federation/federatingdb/announce.go b/internal/federation/federatingdb/announce.go index 7d7b12cbc..e089f7e42 100644 --- a/internal/federation/federatingdb/announce.go +++ b/internal/federation/federatingdb/announce.go @@ -20,16 +20,12 @@ package federatingdb import ( "context" - "encoding/json" "fmt" - "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/ap" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/messages" - "github.com/superseriousbusiness/gotosocial/internal/util" ) func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStreamsAnnounce) error { @@ -38,40 +34,26 @@ func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStre "func": "Announce", }, ) - m, err := streams.Serialize(announce) - if err != nil { - return err + + if l.Level >= logrus.DebugLevel { + i, err := marshalItem(announce) + if err != nil { + return err + } + l = l.WithField("announce", i) + l.Debug("entering Announce") } - b, err := json.Marshal(m) + + targetAcct, fromFederatorChan, err := extractFromCtx(ctx) if err != nil { return err } - - l.Debugf("received ANNOUNCE %s", string(b)) - - targetAcctI := ctx.Value(util.APAccount) - if targetAcctI == nil { - // If the target account wasn't set on the context, that means this request didn't pass through the - // API, but came from inside GtS as the result of another activity on this instance. That being so, + if targetAcct == nil || fromFederatorChan == nil { + // If the target account or federator channel wasn't set on the context, that means this request didn't pass + // through the API, but came from inside GtS as the result of another activity on this instance. That being so, // we can safely just ignore this activity, since we know we've already processed it elsewhere. return nil } - targetAcct, ok := targetAcctI.(*gtsmodel.Account) - if !ok { - l.Error("ANNOUNCE: target account was set on context but couldn't be parsed") - return nil - } - - fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey) - if fromFederatorChanI == nil { - l.Error("ANNOUNCE: from federator channel wasn't set on context") - return nil - } - fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator) - if !ok { - l.Error("ANNOUNCE: from federator channel was set on context but couldn't be parsed") - return nil - } boost, isNew, err := f.typeConverter.ASAnnounceToStatus(ctx, announce) if err != nil { diff --git a/internal/federation/federatingdb/create.go b/internal/federation/federatingdb/create.go index 88b0d1e8b..474890c34 100644 --- a/internal/federation/federatingdb/create.go +++ b/internal/federation/federatingdb/create.go @@ -20,19 +20,15 @@ package federatingdb import ( "context" - "encoding/json" "errors" "fmt" - "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/messages" - "github.com/superseriousbusiness/gotosocial/internal/util" ) // Create adds a new entry to the database which must be able to be @@ -50,44 +46,29 @@ import ( func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { l := f.log.WithFields( logrus.Fields{ - "func": "Create", - "asType": asType.GetTypeName(), + "func": "Create", }, ) - m, err := streams.Serialize(asType) - if err != nil { - return err + + if l.Level >= logrus.DebugLevel { + i, err := marshalItem(asType) + if err != nil { + return err + } + l = l.WithField("create", i) + l.Debug("entering Create") } - b, err := json.Marshal(m) + + targetAcct, fromFederatorChan, err := extractFromCtx(ctx) if err != nil { return err } - - l.Debugf("received CREATE asType %s", string(b)) - - targetAcctI := ctx.Value(util.APAccount) - if targetAcctI == nil { - // If the target account wasn't set on the context, that means this request didn't pass through the - // API, but came from inside GtS as the result of another activity on this instance. That being so, + if targetAcct == nil || fromFederatorChan == nil { + // If the target account or federator channel wasn't set on the context, that means this request didn't pass + // through the API, but came from inside GtS as the result of another activity on this instance. That being so, // we can safely just ignore this activity, since we know we've already processed it elsewhere. return nil } - targetAcct, ok := targetAcctI.(*gtsmodel.Account) - if !ok { - l.Error("CREATE: target account was set on context but couldn't be parsed") - return nil - } - - fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey) - if fromFederatorChanI == nil { - l.Error("CREATE: from federator channel wasn't set on context") - return nil - } - fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator) - if !ok { - l.Error("CREATE: from federator channel was set on context but couldn't be parsed") - return nil - } switch asType.GetTypeName() { case ap.ActivityCreate: diff --git a/internal/federation/federatingdb/delete.go b/internal/federation/federatingdb/delete.go index 9aa36ee90..fc77f8025 100644 --- a/internal/federation/federatingdb/delete.go +++ b/internal/federation/federatingdb/delete.go @@ -27,7 +27,6 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/messages" - "github.com/superseriousbusiness/gotosocial/internal/util" ) // Delete removes the entry with the given id. @@ -40,32 +39,19 @@ func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error { l := f.log.WithFields( logrus.Fields{ "func": "Delete", - "id": id.String(), + "id": id, }, ) - l.Debugf("received DELETE id %s", id.String()) + l.Debug("entering Delete") - targetAcctI := ctx.Value(util.APAccount) - if targetAcctI == nil { - // If the target account wasn't set on the context, that means this request didn't pass through the - // API, but came from inside GtS as the result of another activity on this instance. That being so, - // we can safely just ignore this activity, since we know we've already processed it elsewhere. - return nil - } - targetAcct, ok := targetAcctI.(*gtsmodel.Account) - if !ok { - l.Error("DELETE: target account was set on context but couldn't be parsed") - return nil + targetAcct, fromFederatorChan, err := extractFromCtx(ctx) + if err != nil { + return err } - - fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey) - if fromFederatorChanI == nil { - l.Error("DELETE: from federator channel wasn't set on context") - return nil - } - fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator) - if !ok { - l.Error("DELETE: from federator channel was set on context but couldn't be parsed") + if targetAcct == nil || fromFederatorChan == nil { + // If the target account or federator channel wasn't set on the context, that means this request didn't pass + // through the API, but came from inside GtS as the result of another activity on this instance. That being so, + // we can safely just ignore this activity, since we know we've already processed it elsewhere. return nil } diff --git a/internal/federation/federatingdb/exists.go b/internal/federation/federatingdb/exists.go index 0e13c1196..ec5599c24 100644 --- a/internal/federation/federatingdb/exists.go +++ b/internal/federation/federatingdb/exists.go @@ -29,14 +29,15 @@ import ( // id. It may not be owned by this application instance. // // The library makes this call only after acquiring a lock first. +// +// Implementation note: this just straight up isn't implemented, and doesn't *really* need to be either. func (f *federatingDB) Exists(c context.Context, id *url.URL) (exists bool, err error) { l := f.log.WithFields( logrus.Fields{ "func": "Exists", - "id": id.String(), + "id": id, }, ) - l.Debugf("entering EXISTS function with id %s", id.String()) - + l.Debug("entering Exists") return false, nil } diff --git a/internal/federation/federatingdb/federatingdb_test.go b/internal/federation/federatingdb/federatingdb_test.go index f32314b10..fc78540f2 100644 --- a/internal/federation/federatingdb/federatingdb_test.go +++ b/internal/federation/federatingdb/federatingdb_test.go @@ -18,4 +18,55 @@ package federatingdb_test -// TODO: write tests for pgfed +import ( + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/typeutils" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +type FederatingDBTestSuite struct { + suite.Suite + config *config.Config + db db.DB + log *logrus.Logger + tc typeutils.TypeConverter + federatingDB federatingdb.DB + + testTokens map[string]*gtsmodel.Token + testClients map[string]*gtsmodel.Client + testApplications map[string]*gtsmodel.Application + testUsers map[string]*gtsmodel.User + testAccounts map[string]*gtsmodel.Account + testAttachments map[string]*gtsmodel.MediaAttachment + testStatuses map[string]*gtsmodel.Status + testBlocks map[string]*gtsmodel.Block +} + +func (suite *FederatingDBTestSuite) SetupSuite() { + suite.testTokens = testrig.NewTestTokens() + suite.testClients = testrig.NewTestClients() + suite.testApplications = testrig.NewTestApplications() + suite.testUsers = testrig.NewTestUsers() + suite.testAccounts = testrig.NewTestAccounts() + suite.testAttachments = testrig.NewTestAttachments() + suite.testStatuses = testrig.NewTestStatuses() + suite.testBlocks = testrig.NewTestBlocks() +} + +func (suite *FederatingDBTestSuite) SetupTest() { + suite.config = testrig.NewTestConfig() + suite.db = testrig.NewTestDB() + suite.tc = testrig.NewTestTypeConverter(suite.db) + suite.log = testrig.NewTestLog() + suite.federatingDB = testrig.NewTestFederatingDB(suite.db) + testrig.StandardDBSetup(suite.db, suite.testAccounts) +} + +func (suite *FederatingDBTestSuite) TearDownTest() { + testrig.StandardDBTeardown(suite.db) +} diff --git a/internal/federation/federatingdb/followers.go b/internal/federation/federatingdb/followers.go index 69c68b8b9..61c5d4287 100644 --- a/internal/federation/federatingdb/followers.go +++ b/internal/federation/federatingdb/followers.go @@ -5,12 +5,9 @@ import ( "fmt" "net/url" - "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/util" ) // Followers obtains the Followers Collection for an actor with the @@ -22,39 +19,28 @@ import ( func (f *federatingDB) Followers(ctx context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) { l := f.log.WithFields( logrus.Fields{ - "func": "Followers", - "actorIRI": actorIRI.String(), + "func": "Followers", + "id": actorIRI, }, ) - l.Debugf("entering FOLLOWERS function with actorIRI %s", actorIRI.String()) + l.Debug("entering Followers") - acct := >smodel.Account{} - - if util.IsUserPath(actorIRI) { - acct, err = f.db.GetAccountByURI(ctx, actorIRI.String()) - if err != nil { - return nil, fmt.Errorf("FOLLOWERS: db error getting account with uri %s: %s", actorIRI.String(), err) - } - } else if util.IsFollowersPath(actorIRI) { - if err := f.db.GetWhere(ctx, []db.Where{{Key: "followers_uri", Value: actorIRI.String()}}, acct); err != nil { - return nil, fmt.Errorf("FOLLOWERS: db error getting account with followers uri %s: %s", actorIRI.String(), err) - } - } else { - return nil, fmt.Errorf("FOLLOWERS: could not parse actor IRI %s as users or followers path", actorIRI.String()) + acct, err := f.getAccountForIRI(ctx, actorIRI) + if err != nil { + return nil, err } acctFollowers, err := f.db.GetAccountFollowedBy(ctx, acct.ID, false) if err != nil { - return nil, fmt.Errorf("FOLLOWERS: db error getting followers for account id %s: %s", acct.ID, err) + return nil, fmt.Errorf("Followers: db error getting followers for account id %s: %s", acct.ID, err) } - followers = streams.NewActivityStreamsCollection() - items := streams.NewActivityStreamsItemsProperty() + iris := []*url.URL{} for _, follow := range acctFollowers { if follow.Account == nil { - followAccount, err := f.db.GetAccountByID(ctx, follow.AccountID) + a, err := f.db.GetAccountByID(ctx, follow.AccountID) if err != nil { - errWrapped := fmt.Errorf("FOLLOWERS: db error getting account id %s: %s", follow.AccountID, err) + errWrapped := fmt.Errorf("Followers: db error getting account id %s: %s", follow.AccountID, err) if err == db.ErrNoEntries { // no entry for this account id so it's probably been deleted and we haven't caught up yet l.Error(errWrapped) @@ -64,15 +50,14 @@ func (f *federatingDB) Followers(ctx context.Context, actorIRI *url.URL) (follow return nil, errWrapped } } - follow.Account = followAccount + follow.Account = a } - - uri, err := url.Parse(follow.Account.URI) + u, err := url.Parse(follow.Account.URI) if err != nil { - return nil, fmt.Errorf("FOLLOWERS: error parsing %s as url: %s", follow.Account.URI, err) + return nil, err } - items.AppendIRI(uri) + iris = append(iris, u) } - followers.SetActivityStreamsItems(items) - return + + return f.collectIRIs(ctx, iris) } diff --git a/internal/federation/federatingdb/followers_test.go b/internal/federation/federatingdb/followers_test.go new file mode 100644 index 000000000..d993a7201 --- /dev/null +++ b/internal/federation/federatingdb/followers_test.go @@ -0,0 +1,53 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package federatingdb_test + +import ( + "context" + "encoding/json" + "testing" + + "github.com/go-fed/activity/streams" + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +type FollowersTestSuite struct { + FederatingDBTestSuite +} + +func (suite *FollowersTestSuite) TestGetFollowers() { + testAccount := suite.testAccounts["local_account_2"] + + f, err := suite.federatingDB.Followers(context.Background(), testrig.URLMustParse(testAccount.URI)) + suite.NoError(err) + + fi, err := streams.Serialize(f) + suite.NoError(err) + + fJson, err := json.Marshal(fi) + suite.NoError(err) + + // zork follows local_account_2 so this should be reflected in the response + suite.Equal(`{"@context":"https://www.w3.org/ns/activitystreams","items":"http://localhost:8080/users/the_mighty_zork","type":"Collection"}`, string(fJson)) +} + +func TestFollowersTestSuite(t *testing.T) { + suite.Run(t, &FollowersTestSuite{}) +} diff --git a/internal/federation/federatingdb/following.go b/internal/federation/federatingdb/following.go index a36f4e203..2cc024832 100644 --- a/internal/federation/federatingdb/following.go +++ b/internal/federation/federatingdb/following.go @@ -5,12 +5,9 @@ import ( "fmt" "net/url" - "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/util" ) // Following obtains the Following Collection for an actor with the @@ -22,53 +19,28 @@ import ( func (f *federatingDB) Following(ctx context.Context, actorIRI *url.URL) (following vocab.ActivityStreamsCollection, err error) { l := f.log.WithFields( logrus.Fields{ - "func": "Following", - "actorIRI": actorIRI.String(), + "func": "Following", + "id": actorIRI, }, ) - l.Debugf("entering FOLLOWING function with actorIRI %s", actorIRI.String()) + l.Debug("entering Following") - var acct *gtsmodel.Account - if util.IsUserPath(actorIRI) { - username, err := util.ParseUserPath(actorIRI) - if err != nil { - return nil, fmt.Errorf("FOLLOWING: error parsing user path: %s", err) - } - - a, err := f.db.GetLocalAccountByUsername(ctx, username) - if err != nil { - return nil, fmt.Errorf("FOLLOWING: db error getting account with uri %s: %s", actorIRI.String(), err) - } - - acct = a - } else if util.IsFollowingPath(actorIRI) { - username, err := util.ParseFollowingPath(actorIRI) - if err != nil { - return nil, fmt.Errorf("FOLLOWING: error parsing following path: %s", err) - } - - a, err := f.db.GetLocalAccountByUsername(ctx, username) - if err != nil { - return nil, fmt.Errorf("FOLLOWING: db error getting account with following uri %s: %s", actorIRI.String(), err) - } - - acct = a - } else { - return nil, fmt.Errorf("FOLLOWING: could not parse actor IRI %s as users or following path", actorIRI.String()) + acct, err := f.getAccountForIRI(ctx, actorIRI) + if err != nil { + return nil, err } acctFollowing, err := f.db.GetAccountFollows(ctx, acct.ID) if err != nil { - return nil, fmt.Errorf("FOLLOWING: db error getting following for account id %s: %s", acct.ID, err) + return nil, fmt.Errorf("Following: db error getting following for account id %s: %s", acct.ID, err) } - following = streams.NewActivityStreamsCollection() - items := streams.NewActivityStreamsItemsProperty() + iris := []*url.URL{} for _, follow := range acctFollowing { - if follow.Account == nil { - followAccount, err := f.db.GetAccountByID(ctx, follow.AccountID) + if follow.TargetAccount == nil { + a, err := f.db.GetAccountByID(ctx, follow.TargetAccountID) if err != nil { - errWrapped := fmt.Errorf("FOLLOWING: db error getting account id %s: %s", follow.AccountID, err) + errWrapped := fmt.Errorf("Following: db error getting account id %s: %s", follow.TargetAccountID, err) if err == db.ErrNoEntries { // no entry for this account id so it's probably been deleted and we haven't caught up yet l.Error(errWrapped) @@ -78,15 +50,14 @@ func (f *federatingDB) Following(ctx context.Context, actorIRI *url.URL) (follow return nil, errWrapped } } - follow.Account = followAccount + follow.TargetAccount = a } - - uri, err := url.Parse(follow.Account.URI) + u, err := url.Parse(follow.TargetAccount.URI) if err != nil { - return nil, fmt.Errorf("FOLLOWING: error parsing %s as url: %s", follow.Account.URI, err) + return nil, err } - items.AppendIRI(uri) + iris = append(iris, u) } - following.SetActivityStreamsItems(items) - return + + return f.collectIRIs(ctx, iris) } diff --git a/internal/federation/federatingdb/following_test.go b/internal/federation/federatingdb/following_test.go new file mode 100644 index 000000000..7788840d7 --- /dev/null +++ b/internal/federation/federatingdb/following_test.go @@ -0,0 +1,53 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package federatingdb_test + +import ( + "context" + "encoding/json" + "testing" + + "github.com/go-fed/activity/streams" + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +type FollowingTestSuite struct { + FederatingDBTestSuite +} + +func (suite *FollowingTestSuite) TestGetFollowing() { + testAccount := suite.testAccounts["local_account_1"] + + f, err := suite.federatingDB.Following(context.Background(), testrig.URLMustParse(testAccount.URI)) + suite.NoError(err) + + fi, err := streams.Serialize(f) + suite.NoError(err) + + fJson, err := json.Marshal(fi) + suite.NoError(err) + + // zork follows admin account and local_account_1 + suite.Equal(`{"@context":"https://www.w3.org/ns/activitystreams","items":["http://localhost:8080/users/admin","http://localhost:8080/users/1happyturtle"],"type":"Collection"}`, string(fJson)) +} + +func TestFollowingTestSuite(t *testing.T) { + suite.Run(t, &FollowingTestSuite{}) +} diff --git a/internal/federation/federatingdb/get.go b/internal/federation/federatingdb/get.go index cc04dd851..505891ca4 100644 --- a/internal/federation/federatingdb/get.go +++ b/internal/federation/federatingdb/get.go @@ -25,8 +25,6 @@ import ( "github.com/go-fed/activity/streams/vocab" "github.com/sirupsen/logrus" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/util" ) @@ -37,46 +35,33 @@ func (f *federatingDB) Get(ctx context.Context, id *url.URL) (value vocab.Type, l := f.log.WithFields( logrus.Fields{ "func": "Get", - "id": id.String(), + "id": id, }, ) - l.Debug("entering GET function") + l.Debug("entering Get") if util.IsUserPath(id) { acct, err := f.db.GetAccountByURI(ctx, id.String()) if err != nil { return nil, err } - l.Debug("is user path! returning account") return f.typeConverter.AccountToAS(ctx, acct) } - if util.IsFollowersPath(id) { - acct := >smodel.Account{} - if err := f.db.GetWhere(ctx, []db.Where{{Key: "followers_uri", Value: id.String()}}, acct); err != nil { - return nil, err - } - - followersURI, err := url.Parse(acct.FollowersURI) + if util.IsStatusesPath(id) { + status, err := f.db.GetStatusByURI(ctx, id.String()) if err != nil { return nil, err } + return f.typeConverter.StatusToAS(ctx, status) + } - return f.Followers(ctx, followersURI) + if util.IsFollowersPath(id) { + return f.Followers(ctx, id) } if util.IsFollowingPath(id) { - acct := >smodel.Account{} - if err := f.db.GetWhere(ctx, []db.Where{{Key: "following_uri", Value: id.String()}}, acct); err != nil { - return nil, err - } - - followingURI, err := url.Parse(acct.FollowingURI) - if err != nil { - return nil, err - } - - return f.Following(ctx, followingURI) + return f.Following(ctx, id) } return nil, errors.New("could not get") diff --git a/internal/federation/federatingdb/inbox.go b/internal/federation/federatingdb/inbox.go index 4390a8b4b..95886b571 100644 --- a/internal/federation/federatingdb/inbox.go +++ b/internal/federation/federatingdb/inbox.go @@ -20,44 +20,19 @@ package federatingdb import ( "context" - "fmt" "net/url" - "github.com/go-fed/activity/pub" "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" - "github.com/sirupsen/logrus" - "github.com/superseriousbusiness/gotosocial/internal/util" ) // InboxContains returns true if the OrderedCollection at 'inbox' // contains the specified 'id'. // // The library makes this call only after acquiring a lock first. +// +// Implementation note: we have our own logic for inboxes so always return false here. func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (contains bool, err error) { - l := f.log.WithFields( - logrus.Fields{ - "func": "InboxContains", - "id": id.String(), - }, - ) - l.Debugf("entering INBOXCONTAINS function with for inbox %s and id %s", inbox.String(), id.String()) - - if !util.IsInboxPath(inbox) { - return false, fmt.Errorf("%s is not an inbox URI", inbox.String()) - } - - activityI := c.Value(util.APActivity) - if activityI == nil { - return false, fmt.Errorf("no activity was set for id %s", id.String()) - } - activity, ok := activityI.(pub.Activity) - if !ok || activity == nil { - return false, fmt.Errorf("could not parse contextual activity for id %s", id.String()) - } - - l.Debugf("activity type %s for id %s", activity.GetTypeName(), id.String()) - return false, nil } @@ -65,13 +40,9 @@ func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (con // the specified IRI, for prepending new items. // // The library makes this call only after acquiring a lock first. +// +// Implementation note: we don't (yet) serve inboxes, so just return empty and nil here. func (f *federatingDB) GetInbox(c context.Context, inboxIRI *url.URL) (inbox vocab.ActivityStreamsOrderedCollectionPage, err error) { - l := f.log.WithFields( - logrus.Fields{ - "func": "GetInbox", - }, - ) - l.Debugf("entering GETINBOX function with inboxIRI %s", inboxIRI.String()) return streams.NewActivityStreamsOrderedCollectionPage(), nil } @@ -80,12 +51,8 @@ func (f *federatingDB) GetInbox(c context.Context, inboxIRI *url.URL) (inbox voc // database entries. Separate calls to Create will do that. // // The library makes this call only after acquiring a lock first. +// +// Implementation note: we don't allow inbox setting so just return nil here. func (f *federatingDB) SetInbox(c context.Context, inbox vocab.ActivityStreamsOrderedCollectionPage) error { - l := f.log.WithFields( - logrus.Fields{ - "func": "SetInbox", - }, - ) - l.Debug("entering SETINBOX function") return nil } diff --git a/internal/federation/federatingdb/liked.go b/internal/federation/federatingdb/liked.go index b85398fef..93b3d2c88 100644 --- a/internal/federation/federatingdb/liked.go +++ b/internal/federation/federatingdb/liked.go @@ -22,8 +22,8 @@ import ( "context" "net/url" + "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" - "github.com/sirupsen/logrus" ) // Liked obtains the Liked Collection for an actor with the @@ -32,13 +32,8 @@ import ( // If modified, the library will then call Update. // // The library makes this call only after acquiring a lock first. +// +// Implementation note: we don't serve a Liked collection *yet* so just return an empty collection for now. func (f *federatingDB) Liked(c context.Context, actorIRI *url.URL) (liked vocab.ActivityStreamsCollection, err error) { - l := f.log.WithFields( - logrus.Fields{ - "func": "Liked", - "actorIRI": actorIRI.String(), - }, - ) - l.Debugf("entering LIKED function with actorIRI %s", actorIRI.String()) - return nil, nil + return streams.NewActivityStreamsCollection(), nil } diff --git a/internal/federation/federatingdb/outbox.go b/internal/federation/federatingdb/outbox.go index 81b90aae2..07caf999e 100644 --- a/internal/federation/federatingdb/outbox.go +++ b/internal/federation/federatingdb/outbox.go @@ -20,29 +20,19 @@ package federatingdb import ( "context" - "fmt" "net/url" "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" - "github.com/sirupsen/logrus" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/util" ) // GetOutbox returns the first ordered collection page of the outbox // at the specified IRI, for prepending new items. // // The library makes this call only after acquiring a lock first. +// +// Implementation note: we don't (yet) serve outboxes, so just return empty and nil here. func (f *federatingDB) GetOutbox(ctx context.Context, outboxIRI *url.URL) (inbox vocab.ActivityStreamsOrderedCollectionPage, err error) { - l := f.log.WithFields( - logrus.Fields{ - "func": "GetOutbox", - }, - ) - l.Debug("entering GETOUTBOX function") - return streams.NewActivityStreamsOrderedCollectionPage(), nil } @@ -51,14 +41,9 @@ func (f *federatingDB) GetOutbox(ctx context.Context, outboxIRI *url.URL) (inbox // database entries. Separate calls to Create will do that. // // The library makes this call only after acquiring a lock first. +// +// Implementation note: we don't allow outbox setting so just return nil here. func (f *federatingDB) SetOutbox(ctx context.Context, outbox vocab.ActivityStreamsOrderedCollectionPage) error { - l := f.log.WithFields( - logrus.Fields{ - "func": "SetOutbox", - }, - ) - l.Debug("entering SETOUTBOX function") - return nil } @@ -67,23 +52,9 @@ func (f *federatingDB) SetOutbox(ctx context.Context, outbox vocab.ActivityStrea // // The library makes this call only after acquiring a lock first. func (f *federatingDB) OutboxForInbox(ctx context.Context, inboxIRI *url.URL) (outboxIRI *url.URL, err error) { - l := f.log.WithFields( - logrus.Fields{ - "func": "OutboxForInbox", - "inboxIRI": inboxIRI.String(), - }, - ) - l.Debugf("entering OUTBOXFORINBOX function with inboxIRI %s", inboxIRI.String()) - - if !util.IsInboxPath(inboxIRI) { - return nil, fmt.Errorf("%s is not an inbox URI", inboxIRI.String()) - } - acct := >smodel.Account{} - if err := f.db.GetWhere(ctx, []db.Where{{Key: "inbox_uri", Value: inboxIRI.String()}}, acct); err != nil { - if err == db.ErrNoEntries { - return nil, fmt.Errorf("no actor found that corresponds to inbox %s", inboxIRI.String()) - } - return nil, fmt.Errorf("db error searching for actor with inbox %s", inboxIRI.String()) + acct, err := f.getAccountForIRI(ctx, inboxIRI) + if err != nil { + return nil, err } return url.Parse(acct.OutboxURI) } diff --git a/internal/federation/federatingdb/owns.go b/internal/federation/federatingdb/owns.go index 1c1f2512d..04a417490 100644 --- a/internal/federation/federatingdb/owns.go +++ b/internal/federation/federatingdb/owns.go @@ -36,10 +36,10 @@ func (f *federatingDB) Owns(ctx context.Context, id *url.URL) (bool, error) { l := f.log.WithFields( logrus.Fields{ "func": "Owns", - "id": id.String(), + "id": id, }, ) - l.Tracef("entering OWNS function with id %s", id.String()) + l.Debug("entering Owns") // if the id host isn't this instance host, we don't own this IRI if id.Host != f.config.Host { diff --git a/internal/federation/federatingdb/undo.go b/internal/federation/federatingdb/undo.go index 481c2d787..9fcb6ce1a 100644 --- a/internal/federation/federatingdb/undo.go +++ b/internal/federation/federatingdb/undo.go @@ -20,48 +20,42 @@ package federatingdb import ( "context" - "encoding/json" "errors" "fmt" - "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/util" ) func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) error { l := f.log.WithFields( logrus.Fields{ - "func": "Undo", - "asType": undo.GetTypeName(), + "func": "Undo", }, ) - m, err := streams.Serialize(undo) - if err != nil { - return err + + if l.Level >= logrus.DebugLevel { + i, err := marshalItem(undo) + if err != nil { + return err + } + l = l.WithField("undo", i) + l.Debug("entering Undo") } - b, err := json.Marshal(m) + + targetAcct, fromFederatorChan, err := extractFromCtx(ctx) if err != nil { return err } - l.Debugf("received UNDO asType %s", string(b)) - - targetAcctI := ctx.Value(util.APAccount) - if targetAcctI == nil { - // If the target account wasn't set on the context, that means this request didn't pass through the - // API, but came from inside GtS as the result of another activity on this instance. That being so, + if targetAcct == nil || fromFederatorChan == nil { + // If the target account or federator channel wasn't set on the context, that means this request didn't pass + // through the API, but came from inside GtS as the result of another activity on this instance. That being so, // we can safely just ignore this activity, since we know we've already processed it elsewhere. return nil } - targetAcct, ok := targetAcctI.(*gtsmodel.Account) - if !ok { - l.Error("UNDO: target account was set on context but couldn't be parsed") - return nil - } undoObject := undo.GetActivityStreamsObject() if undoObject == nil { diff --git a/internal/federation/federatingdb/update.go b/internal/federation/federatingdb/update.go index 5dec2bd69..9e7bd3d5b 100644 --- a/internal/federation/federatingdb/update.go +++ b/internal/federation/federatingdb/update.go @@ -20,11 +20,9 @@ package federatingdb import ( "context" - "encoding/json" "errors" "fmt" - "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/ap" @@ -45,35 +43,32 @@ import ( func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error { l := f.log.WithFields( logrus.Fields{ - "func": "Update", - "asType": asType.GetTypeName(), + "func": "Update", }, ) - m, err := streams.Serialize(asType) - if err != nil { - return err + + if l.Level >= logrus.DebugLevel { + i, err := marshalItem(asType) + if err != nil { + return err + } + l = l.WithField("update", i) + l.Debug("entering Update") } - b, err := json.Marshal(m) + + targetAcct, fromFederatorChan, err := extractFromCtx(ctx) if err != nil { return err } - - l.Debugf("received UPDATE asType %s", string(b)) - - targetAcctI := ctx.Value(util.APAccount) - if targetAcctI == nil { - // If the target account wasn't set on the context, that means this request didn't pass through the - // API, but came from inside GtS as the result of another activity on this instance. That being so, + if targetAcct == nil || fromFederatorChan == nil { + // If the target account or federator channel wasn't set on the context, that means this request didn't pass + // through the API, but came from inside GtS as the result of another activity on this instance. That being so, // we can safely just ignore this activity, since we know we've already processed it elsewhere. return nil } - targetAcct, ok := targetAcctI.(*gtsmodel.Account) - if !ok { - l.Error("UPDATE: target account was set on context but couldn't be parsed") - } requestingAcctI := ctx.Value(util.APRequestingAccount) - if targetAcctI == nil { + if requestingAcctI == nil { l.Error("UPDATE: requesting account wasn't set on context") } requestingAcct, ok := requestingAcctI.(*gtsmodel.Account) @@ -81,15 +76,6 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error { l.Error("UPDATE: requesting account was set on context but couldn't be parsed") } - fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey) - if fromFederatorChanI == nil { - l.Error("UPDATE: from federator channel wasn't set on context") - } - fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator) - if !ok { - l.Error("UPDATE: from federator channel was set on context but couldn't be parsed") - } - typeName := asType.GetTypeName() if typeName == ap.ActorApplication || typeName == ap.ActorGroup || diff --git a/internal/federation/federatingdb/util.go b/internal/federation/federatingdb/util.go index d8c7d8e8a..d719bf16d 100644 --- a/internal/federation/federatingdb/util.go +++ b/internal/federation/federatingdb/util.go @@ -32,6 +32,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" + "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/util" ) @@ -64,19 +65,18 @@ func sameActor(activityActor vocab.ActivityStreamsActorProperty, followActor voc func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL, err error) { l := f.log.WithFields( logrus.Fields{ - "func": "NewID", - "asType": t.GetTypeName(), + "func": "NewID", }, ) - m, err := streams.Serialize(t) - if err != nil { - return nil, err - } - b, err := json.Marshal(m) - if err != nil { - return nil, err + + if l.Level >= logrus.DebugLevel { + i, err := marshalItem(t) + if err != nil { + return nil, err + } + l = l.WithField("newID", i) + l.Debug("entering NewID") } - l.Debugf("received NEWID request for asType %s", string(b)) switch t.GetTypeName() { case ap.ActivityFollow: @@ -201,23 +201,9 @@ func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL, // // The library makes this call only after acquiring a lock first. func (f *federatingDB) ActorForOutbox(ctx context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error) { - l := f.log.WithFields( - logrus.Fields{ - "func": "ActorForOutbox", - "inboxIRI": outboxIRI.String(), - }, - ) - l.Debugf("entering ACTORFOROUTBOX function with outboxIRI %s", outboxIRI.String()) - - if !util.IsOutboxPath(outboxIRI) { - return nil, fmt.Errorf("%s is not an outbox URI", outboxIRI.String()) - } - acct := >smodel.Account{} - if err := f.db.GetWhere(ctx, []db.Where{{Key: "outbox_uri", Value: outboxIRI.String()}}, acct); err != nil { - if err == db.ErrNoEntries { - return nil, fmt.Errorf("no actor found that corresponds to outbox %s", outboxIRI.String()) - } - return nil, fmt.Errorf("db error searching for actor with outbox %s", outboxIRI.String()) + acct, err := f.getAccountForIRI(ctx, outboxIRI) + if err != nil { + return nil, err } return url.Parse(acct.URI) } @@ -226,23 +212,116 @@ func (f *federatingDB) ActorForOutbox(ctx context.Context, outboxIRI *url.URL) ( // // The library makes this call only after acquiring a lock first. func (f *federatingDB) ActorForInbox(ctx context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error) { - l := f.log.WithFields( - logrus.Fields{ - "func": "ActorForInbox", - "inboxIRI": inboxIRI.String(), - }, - ) - l.Debugf("entering ACTORFORINBOX function with inboxIRI %s", inboxIRI.String()) - - if !util.IsInboxPath(inboxIRI) { - return nil, fmt.Errorf("%s is not an inbox URI", inboxIRI.String()) + acct, err := f.getAccountForIRI(ctx, inboxIRI) + if err != nil { + return nil, err } + return url.Parse(acct.URI) +} + +// getAccountForIRI returns the account that corresponds to or owns the given IRI. +func (f *federatingDB) getAccountForIRI(ctx context.Context, iri *url.URL) (account *gtsmodel.Account, err error) { acct := >smodel.Account{} - if err := f.db.GetWhere(ctx, []db.Where{{Key: "inbox_uri", Value: inboxIRI.String()}}, acct); err != nil { - if err == db.ErrNoEntries { - return nil, fmt.Errorf("no actor found that corresponds to inbox %s", inboxIRI.String()) + + if util.IsInboxPath(iri) { + if err := f.db.GetWhere(ctx, []db.Where{{Key: "inbox_uri", Value: iri.String()}}, acct); err != nil { + if err == db.ErrNoEntries { + return nil, fmt.Errorf("no actor found that corresponds to inbox %s", iri.String()) + } + return nil, fmt.Errorf("db error searching for actor with inbox %s", iri.String()) } - return nil, fmt.Errorf("db error searching for actor with inbox %s", inboxIRI.String()) + return acct, nil } - return url.Parse(acct.URI) + + if util.IsOutboxPath(iri) { + if err := f.db.GetWhere(ctx, []db.Where{{Key: "outbox_uri", Value: iri.String()}}, acct); err != nil { + if err == db.ErrNoEntries { + return nil, fmt.Errorf("no actor found that corresponds to outbox %s", iri.String()) + } + return nil, fmt.Errorf("db error searching for actor with outbox %s", iri.String()) + } + return acct, nil + } + + if util.IsUserPath(iri) { + if err := f.db.GetWhere(ctx, []db.Where{{Key: "uri", Value: iri.String()}}, acct); err != nil { + if err == db.ErrNoEntries { + return nil, fmt.Errorf("no actor found that corresponds to uri %s", iri.String()) + } + return nil, fmt.Errorf("db error searching for actor with uri %s", iri.String()) + } + return acct, nil + } + + if util.IsFollowersPath(iri) { + if err := f.db.GetWhere(ctx, []db.Where{{Key: "followers_uri", Value: iri.String()}}, acct); err != nil { + if err == db.ErrNoEntries { + return nil, fmt.Errorf("no actor found that corresponds to followers_uri %s", iri.String()) + } + return nil, fmt.Errorf("db error searching for actor with followers_uri %s", iri.String()) + } + return acct, nil + } + + if util.IsFollowingPath(iri) { + if err := f.db.GetWhere(ctx, []db.Where{{Key: "following_uri", Value: iri.String()}}, acct); err != nil { + if err == db.ErrNoEntries { + return nil, fmt.Errorf("no actor found that corresponds to following_uri %s", iri.String()) + } + return nil, fmt.Errorf("db error searching for actor with following_uri %s", iri.String()) + } + return acct, nil + } + + return nil, fmt.Errorf("getActorForIRI: iri %s not recognised", iri) +} + +// collectFollows takes a slice of iris and converts them into ActivityStreamsCollection of IRIs. +func (f *federatingDB) collectIRIs(ctx context.Context, iris []*url.URL) (vocab.ActivityStreamsCollection, error) { + collection := streams.NewActivityStreamsCollection() + items := streams.NewActivityStreamsItemsProperty() + for _, i := range iris { + items.AppendIRI(i) + } + collection.SetActivityStreamsItems(items) + return collection, nil +} + +// extractFromCtx extracts some useful values from a context passed into the federatingDB via the API: +// - The target account that owns the inbox or URI being interacted with. +// - A channel that messages for the processor can be placed into. +func extractFromCtx(ctx context.Context) (*gtsmodel.Account, chan messages.FromFederator, error) { + var targetAcct *gtsmodel.Account + targetAcctI := ctx.Value(util.APAccount) + if targetAcctI != nil { + var ok bool + targetAcct, ok = targetAcctI.(*gtsmodel.Account) + if !ok { + return nil, nil, errors.New("extractFromCtx: account value in context not parseable") + } + } + + var fromFederatorChan chan messages.FromFederator + fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey) + if fromFederatorChanI != nil { + var ok bool + fromFederatorChan, ok = fromFederatorChanI.(chan messages.FromFederator) + if !ok { + return nil, nil, errors.New("extractFromCtx: fromFederatorChan value in context not parseable") + } + } + + return targetAcct, fromFederatorChan, nil +} + +func marshalItem(item vocab.Type) (string, error) { + m, err := streams.Serialize(item) + if err != nil { + return "", err + } + b, err := json.Marshal(m) + if err != nil { + return "", err + } + return string(b), nil } |