Handle forwarded messages (#273)
* correct path of foss_satan * add APIri and notes * test create forward note * rename target => receiving account * split up create into separate funcs * update extractFromCtx * tidy up from federator processing * foss satan => http not https * check if status in db * mock dereference of status from IRI * add forward message deref test * update test with activities * add remote_account_2 to test rig
This commit is contained in:
parent
3dc7644ae6
commit
367bdca250
|
@ -48,12 +48,9 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
|||
l.Debug("entering Accept")
|
||||
}
|
||||
|
||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
receivingAccount, _, fromFederatorChan := extractFromCtx(ctx)
|
||||
if receivingAccount == nil || fromFederatorChan == nil {
|
||||
// If the receiving 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
|
||||
|
@ -77,7 +74,7 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
|||
}
|
||||
|
||||
// make sure the addressee of the original follow is the same as whatever inbox this landed in
|
||||
if gtsFollowRequest.AccountID != targetAcct.ID {
|
||||
if gtsFollowRequest.AccountID != receivingAccount.ID {
|
||||
return errors.New("ACCEPT: follow object account and inbox account were not the same")
|
||||
}
|
||||
follow, err := f.db.AcceptFollowRequest(ctx, gtsFollowRequest.AccountID, gtsFollowRequest.TargetAccountID)
|
||||
|
@ -89,7 +86,7 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
|||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityAccept,
|
||||
GTSModel: follow,
|
||||
ReceivingAccount: targetAcct,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -114,7 +111,7 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
|||
return fmt.Errorf("ACCEPT: error converting asfollow to gtsfollow: %s", err)
|
||||
}
|
||||
// make sure the addressee of the original follow is the same as whatever inbox this landed in
|
||||
if gtsFollow.AccountID != targetAcct.ID {
|
||||
if gtsFollow.AccountID != receivingAccount.ID {
|
||||
return errors.New("ACCEPT: follow object account and inbox account were not the same")
|
||||
}
|
||||
follow, err := f.db.AcceptFollowRequest(ctx, gtsFollow.AccountID, gtsFollow.TargetAccountID)
|
||||
|
@ -126,7 +123,7 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
|||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityAccept,
|
||||
GTSModel: follow,
|
||||
ReceivingAccount: targetAcct,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -44,12 +44,9 @@ func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStre
|
|||
l.Debug("entering Announce")
|
||||
}
|
||||
|
||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
receivingAccount, _, fromFederatorChan := extractFromCtx(ctx)
|
||||
if receivingAccount == nil || fromFederatorChan == nil {
|
||||
// If the receiving 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
|
||||
|
@ -57,7 +54,7 @@ func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStre
|
|||
|
||||
boost, isNew, err := f.typeConverter.ASAnnounceToStatus(ctx, announce)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ANNOUNCE: error converting announce to boost: %s", err)
|
||||
return fmt.Errorf("Announce: error converting announce to boost: %s", err)
|
||||
}
|
||||
|
||||
if !isNew {
|
||||
|
@ -70,7 +67,7 @@ func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStre
|
|||
APObjectType: ap.ActivityAnnounce,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: boost,
|
||||
ReceivingAccount: targetAcct,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -22,11 +22,13 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
@ -59,33 +61,166 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
|||
l.Debug("entering Create")
|
||||
}
|
||||
|
||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
receivingAccount, requestingAccount, fromFederatorChan := extractFromCtx(ctx)
|
||||
if receivingAccount == nil || fromFederatorChan == nil {
|
||||
// If the receiving 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
|
||||
}
|
||||
|
||||
switch asType.GetTypeName() {
|
||||
case ap.ActivityBlock:
|
||||
// BLOCK SOMETHING
|
||||
return f.activityBlock(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan)
|
||||
case ap.ActivityCreate:
|
||||
// CREATE SOMETHING
|
||||
return f.activityCreate(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan)
|
||||
case ap.ActivityFollow:
|
||||
// FOLLOW SOMETHING
|
||||
return f.activityFollow(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan)
|
||||
case ap.ActivityLike:
|
||||
// LIKE SOMETHING
|
||||
return f.activityLike(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
BLOCK HANDLERS
|
||||
*/
|
||||
|
||||
func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, receiving *gtsmodel.Account, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) error {
|
||||
blockable, ok := asType.(vocab.ActivityStreamsBlock)
|
||||
if !ok {
|
||||
return errors.New("activityBlock: could not convert type to block")
|
||||
}
|
||||
|
||||
block, err := f.typeConverter.ASBlockToBlock(ctx, blockable)
|
||||
if err != nil {
|
||||
return fmt.Errorf("activityBlock: could not convert Block to gts model block")
|
||||
}
|
||||
|
||||
newID, err := id.NewULID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block.ID = newID
|
||||
|
||||
if err := f.db.Put(ctx, block); err != nil {
|
||||
return fmt.Errorf("activityBlock: database error inserting block: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ActivityBlock,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: block,
|
||||
ReceivingAccount: receiving,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
CREATE HANDLERS
|
||||
*/
|
||||
|
||||
func (f *federatingDB) activityCreate(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) error {
|
||||
create, ok := asType.(vocab.ActivityStreamsCreate)
|
||||
if !ok {
|
||||
return errors.New("CREATE: could not convert type to create")
|
||||
return errors.New("activityCreate: could not convert type to create")
|
||||
}
|
||||
|
||||
// create should have an object
|
||||
object := create.GetActivityStreamsObject()
|
||||
if object == nil {
|
||||
return errors.New("Create had no Object")
|
||||
}
|
||||
|
||||
errs := []string{}
|
||||
// iterate through the object(s) to see what we're meant to be creating
|
||||
for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() {
|
||||
switch objectIter.GetType().GetTypeName() {
|
||||
asObjectType := objectIter.GetType()
|
||||
if asObjectType == nil {
|
||||
// currently we can't do anything with just a Create of something that's not an Object with a type
|
||||
// TODO: process a Create with an Object that's just a URI or something
|
||||
errs = append(errs, "object of Create was not a Type")
|
||||
continue
|
||||
}
|
||||
|
||||
// we have a type -- what is it?
|
||||
asObjectTypeName := asObjectType.GetTypeName()
|
||||
switch asObjectTypeName {
|
||||
case ap.ObjectNote:
|
||||
// CREATE A NOTE
|
||||
note := objectIter.GetActivityStreamsNote()
|
||||
if err := f.createNote(ctx, objectIter.GetActivityStreamsNote(), receivingAccount, requestingAccount, fromFederatorChan); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
default:
|
||||
errs = append(errs, fmt.Sprintf("received an object on a Create that we couldn't handle: %s", asObjectType.GetTypeName()))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
return fmt.Errorf("activityCreate: one or more errors while processing activity: %s", strings.Join(errs, "; "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createNote handles a Create activity with a Note type.
|
||||
func (f *federatingDB) createNote(ctx context.Context, note vocab.ActivityStreamsNote, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) error {
|
||||
l := f.log.WithFields(logrus.Fields{
|
||||
"func": "createNote",
|
||||
"receivingAccount": receivingAccount.URI,
|
||||
"requestingAccount": requestingAccount.URI,
|
||||
})
|
||||
|
||||
// Check if we have a forward.
|
||||
// In other words, was the note posted to our inbox by at least one actor who actually created the note, or are they just forwarding it?
|
||||
forward := true
|
||||
|
||||
// note should have an attributedTo
|
||||
noteAttributedTo := note.GetActivityStreamsAttributedTo()
|
||||
if noteAttributedTo == nil {
|
||||
return errors.New("createNote: note had no attributedTo")
|
||||
}
|
||||
|
||||
// compare the attributedTo(s) with the actor who posted this to our inbox
|
||||
for attributedToIter := noteAttributedTo.Begin(); attributedToIter != noteAttributedTo.End(); attributedToIter = attributedToIter.Next() {
|
||||
if !attributedToIter.IsIRI() {
|
||||
continue
|
||||
}
|
||||
iri := attributedToIter.GetIRI()
|
||||
if requestingAccount.URI == iri.String() {
|
||||
// at least one creator of the note, and the actor who posted the note to our inbox, are the same, so it's not a forward
|
||||
forward = false
|
||||
}
|
||||
}
|
||||
|
||||
// If we do have a forward, we should ignore the content for now and just dereference based on the URL/ID of the note instead, to get the note straight from the horse's mouth
|
||||
if forward {
|
||||
l.Trace("note is a forward")
|
||||
id := note.GetJSONLDId()
|
||||
if !id.IsIRI() {
|
||||
// if the note id isn't an IRI, there's nothing we can do here
|
||||
return nil
|
||||
}
|
||||
// pass the note iri into the processor and have it do the dereferencing instead of doing it here
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APIri: id.GetIRI(),
|
||||
GTSModel: nil,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we reach this point, we know it's not a forwarded status, so proceed with processing it as normal
|
||||
|
||||
status, err := f.typeConverter.ASStatusToStatus(ctx, note)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CREATE: error converting note to status: %s", err)
|
||||
return fmt.Errorf("createNote: error converting note to status: %s", err)
|
||||
}
|
||||
|
||||
// id the status based on the time it was created
|
||||
|
@ -102,27 +237,32 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
|||
return nil
|
||||
}
|
||||
// an actual error has happened
|
||||
return fmt.Errorf("CREATE: database error inserting status: %s", err)
|
||||
return fmt.Errorf("createNote: database error inserting status: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
ReceivingAccount: targetAcct,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case ap.ActivityFollow:
|
||||
// FOLLOW SOMETHING
|
||||
|
||||
/*
|
||||
FOLLOW HANDLERS
|
||||
*/
|
||||
|
||||
func (f *federatingDB) activityFollow(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) error {
|
||||
follow, ok := asType.(vocab.ActivityStreamsFollow)
|
||||
if !ok {
|
||||
return errors.New("CREATE: could not convert type to follow")
|
||||
return errors.New("activityFollow: could not convert type to follow")
|
||||
}
|
||||
|
||||
followRequest, err := f.typeConverter.ASFollowToFollowRequest(ctx, follow)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CREATE: could not convert Follow to follow request: %s", err)
|
||||
return fmt.Errorf("activityFollow: could not convert Follow to follow request: %s", err)
|
||||
}
|
||||
|
||||
newID, err := id.NewULID()
|
||||
|
@ -132,25 +272,32 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
|||
followRequest.ID = newID
|
||||
|
||||
if err := f.db.Put(ctx, followRequest); err != nil {
|
||||
return fmt.Errorf("CREATE: database error inserting follow request: %s", err)
|
||||
return fmt.Errorf("activityFollow: database error inserting follow request: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: followRequest,
|
||||
ReceivingAccount: targetAcct,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
case ap.ActivityLike:
|
||||
// LIKE SOMETHING
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
LIKE HANDLERS
|
||||
*/
|
||||
|
||||
func (f *federatingDB) activityLike(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) error {
|
||||
like, ok := asType.(vocab.ActivityStreamsLike)
|
||||
if !ok {
|
||||
return errors.New("CREATE: could not convert type to like")
|
||||
return errors.New("activityLike: could not convert type to like")
|
||||
}
|
||||
|
||||
fave, err := f.typeConverter.ASLikeToFave(ctx, like)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CREATE: could not convert Like to fave: %s", err)
|
||||
return fmt.Errorf("activityLike: could not convert Like to fave: %s", err)
|
||||
}
|
||||
|
||||
newID, err := id.NewULID()
|
||||
|
@ -160,43 +307,15 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
|||
fave.ID = newID
|
||||
|
||||
if err := f.db.Put(ctx, fave); err != nil {
|
||||
return fmt.Errorf("CREATE: database error inserting fave: %s", err)
|
||||
return fmt.Errorf("activityLike: database error inserting fave: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ActivityLike,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: fave,
|
||||
ReceivingAccount: targetAcct,
|
||||
}
|
||||
case ap.ActivityBlock:
|
||||
// BLOCK SOMETHING
|
||||
blockable, ok := asType.(vocab.ActivityStreamsBlock)
|
||||
if !ok {
|
||||
return errors.New("CREATE: could not convert type to block")
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
|
||||
block, err := f.typeConverter.ASBlockToBlock(ctx, blockable)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CREATE: could not convert Block to gts model block")
|
||||
}
|
||||
|
||||
newID, err := id.NewULID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block.ID = newID
|
||||
|
||||
if err := f.db.Put(ctx, block); err != nil {
|
||||
return fmt.Errorf("CREATE: database error inserting block: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ActivityBlock,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: block,
|
||||
ReceivingAccount: targetAcct,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
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"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
)
|
||||
|
||||
type CreateTestSuite struct {
|
||||
FederatingDBTestSuite
|
||||
}
|
||||
|
||||
func (suite *CreateTestSuite) TestCreateNote() {
|
||||
receivingAccount := suite.testAccounts["local_account_1"]
|
||||
requestingAccount := suite.testAccounts["remote_account_1"]
|
||||
fromFederatorChan := make(chan messages.FromFederator, 10)
|
||||
|
||||
ctx := createTestContext(receivingAccount, requestingAccount, fromFederatorChan)
|
||||
|
||||
create := suite.testActivities["dm_for_zork"].Activity
|
||||
|
||||
err := suite.federatingDB.Create(ctx, create)
|
||||
suite.NoError(err)
|
||||
|
||||
// should be a message heading to the processor now, which we can intercept here
|
||||
msg := <-fromFederatorChan
|
||||
suite.Equal(ap.ObjectNote, msg.APObjectType)
|
||||
suite.Equal(ap.ActivityCreate, msg.APActivityType)
|
||||
|
||||
// shiny new status should be defined on the message
|
||||
suite.NotNil(msg.GTSModel)
|
||||
status := msg.GTSModel.(*gtsmodel.Status)
|
||||
|
||||
// status should have some expected values
|
||||
suite.Equal(requestingAccount.ID, status.AccountID)
|
||||
suite.Equal("hey zork here's a new private note for you", status.Content)
|
||||
|
||||
// status should be in the database
|
||||
_, err = suite.db.GetStatusByID(context.Background(), status.ID)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *CreateTestSuite) TestCreateNoteForward() {
|
||||
receivingAccount := suite.testAccounts["local_account_1"]
|
||||
requestingAccount := suite.testAccounts["remote_account_1"]
|
||||
fromFederatorChan := make(chan messages.FromFederator, 10)
|
||||
|
||||
ctx := createTestContext(receivingAccount, requestingAccount, fromFederatorChan)
|
||||
|
||||
create := suite.testActivities["forwarded_message"].Activity
|
||||
|
||||
err := suite.federatingDB.Create(ctx, create)
|
||||
suite.NoError(err)
|
||||
|
||||
// should be a message heading to the processor now, which we can intercept here
|
||||
msg := <-fromFederatorChan
|
||||
suite.Equal(ap.ObjectNote, msg.APObjectType)
|
||||
suite.Equal(ap.ActivityCreate, msg.APActivityType)
|
||||
|
||||
// nothing should be set as the model since this is a forward
|
||||
suite.Nil(msg.GTSModel)
|
||||
|
||||
// but we should have a uri set
|
||||
suite.Equal("http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1", msg.APIri.String())
|
||||
}
|
||||
|
||||
func TestCreateTestSuite(t *testing.T) {
|
||||
suite.Run(t, &CreateTestSuite{})
|
||||
}
|
|
@ -44,12 +44,9 @@ func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error {
|
|||
)
|
||||
l.Debug("entering Delete")
|
||||
|
||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
receivingAccount, _, fromFederatorChan := extractFromCtx(ctx)
|
||||
if receivingAccount == nil || fromFederatorChan == nil {
|
||||
// If the receiving 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
|
||||
|
@ -68,7 +65,7 @@ func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error {
|
|||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
GTSModel: s,
|
||||
ReceivingAccount: targetAcct,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,7 +77,7 @@ func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error {
|
|||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
GTSModel: a,
|
||||
ReceivingAccount: targetAcct,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,13 +19,17 @@
|
|||
package federatingdb_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -45,6 +49,7 @@ type FederatingDBTestSuite struct {
|
|||
testAttachments map[string]*gtsmodel.MediaAttachment
|
||||
testStatuses map[string]*gtsmodel.Status
|
||||
testBlocks map[string]*gtsmodel.Block
|
||||
testActivities map[string]testrig.ActivityWithSignature
|
||||
}
|
||||
|
||||
func (suite *FederatingDBTestSuite) SetupSuite() {
|
||||
|
@ -56,6 +61,7 @@ func (suite *FederatingDBTestSuite) SetupSuite() {
|
|||
suite.testAttachments = testrig.NewTestAttachments()
|
||||
suite.testStatuses = testrig.NewTestStatuses()
|
||||
suite.testBlocks = testrig.NewTestBlocks()
|
||||
suite.testActivities = testrig.NewTestActivities(suite.testAccounts)
|
||||
}
|
||||
|
||||
func (suite *FederatingDBTestSuite) SetupTest() {
|
||||
|
@ -70,3 +76,11 @@ func (suite *FederatingDBTestSuite) SetupTest() {
|
|||
func (suite *FederatingDBTestSuite) TearDownTest() {
|
||||
testrig.StandardDBTeardown(suite.db)
|
||||
}
|
||||
|
||||
func createTestContext(receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) context.Context {
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, util.APReceivingAccount, receivingAccount)
|
||||
ctx = context.WithValue(ctx, util.APRequestingAccount, requestingAccount)
|
||||
ctx = context.WithValue(ctx, util.APFromFederatorChanKey, fromFederatorChan)
|
||||
return ctx
|
||||
}
|
||||
|
|
|
@ -46,12 +46,9 @@ func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo)
|
|||
l.Debug("entering Undo")
|
||||
}
|
||||
|
||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
receivingAccount, _, fromFederatorChan := extractFromCtx(ctx)
|
||||
if receivingAccount == nil || fromFederatorChan == nil {
|
||||
// If the receiving 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
|
||||
|
@ -83,7 +80,7 @@ func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo)
|
|||
return fmt.Errorf("UNDO: error converting asfollow to gtsfollow: %s", err)
|
||||
}
|
||||
// make sure the addressee of the original follow is the same as whatever inbox this landed in
|
||||
if gtsFollow.TargetAccountID != targetAcct.ID {
|
||||
if gtsFollow.TargetAccountID != receivingAccount.ID {
|
||||
return errors.New("UNDO: follow object account and inbox account were not the same")
|
||||
}
|
||||
// delete any existing FOLLOW
|
||||
|
@ -116,7 +113,7 @@ func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo)
|
|||
return fmt.Errorf("UNDO: error converting asblock to gtsblock: %s", err)
|
||||
}
|
||||
// make sure the addressee of the original block is the same as whatever inbox this landed in
|
||||
if gtsBlock.TargetAccountID != targetAcct.ID {
|
||||
if gtsBlock.TargetAccountID != receivingAccount.ID {
|
||||
return errors.New("UNDO: block object account and inbox account were not the same")
|
||||
}
|
||||
// delete any existing BLOCK
|
||||
|
|
|
@ -56,12 +56,9 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
|||
l.Debug("entering Update")
|
||||
}
|
||||
|
||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
receivingAccount, _, fromFederatorChan := extractFromCtx(ctx)
|
||||
if receivingAccount == nil || fromFederatorChan == nil {
|
||||
// If the receiving 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
|
||||
|
@ -153,7 +150,7 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
|||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityUpdate,
|
||||
GTSModel: updatedAcct,
|
||||
ReceivingAccount: targetAcct,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -289,29 +289,26 @@ func (f *federatingDB) collectIRIs(ctx context.Context, iris []*url.URL) (vocab.
|
|||
|
||||
// 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.
|
||||
// - The requesting account that posted to the inbox.
|
||||
// - 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")
|
||||
}
|
||||
// If a value is not present, nil will be returned for it. It's up to the caller to check this and respond appropriately.
|
||||
func extractFromCtx(ctx context.Context) (receivingAccount, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) {
|
||||
receivingAccountI := ctx.Value(util.APReceivingAccount)
|
||||
if receivingAccountI != nil {
|
||||
receivingAccount = receivingAccountI.(*gtsmodel.Account)
|
||||
}
|
||||
|
||||
requestingAcctI := ctx.Value(util.APRequestingAccount)
|
||||
if requestingAcctI != nil {
|
||||
requestingAccount = requestingAcctI.(*gtsmodel.Account)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
fromFederatorChan = fromFederatorChanI.(chan messages.FromFederator)
|
||||
}
|
||||
|
||||
return targetAcct, fromFederatorChan, nil
|
||||
return
|
||||
}
|
||||
|
||||
func marshalItem(item vocab.Type) (string, error) {
|
||||
|
|
|
@ -113,12 +113,12 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
|
|||
return nil, false, errors.New("username was empty")
|
||||
}
|
||||
|
||||
requestedAccount, err := f.db.GetLocalAccountByUsername(ctx, username)
|
||||
receivingAccount, err := f.db.GetLocalAccountByUsername(ctx, username)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("could not fetch requested account with username %s: %s", username, err)
|
||||
return nil, false, fmt.Errorf("could not fetch receiving account with username %s: %s", username, err)
|
||||
}
|
||||
|
||||
publicKeyOwnerURI, authenticated, err := f.AuthenticateFederatedRequest(ctx, requestedAccount.Username)
|
||||
publicKeyOwnerURI, authenticated, err := f.AuthenticateFederatedRequest(ctx, receivingAccount.Username)
|
||||
if err != nil {
|
||||
l.Debugf("request not authenticated: %s", err)
|
||||
return ctx, false, err
|
||||
|
@ -154,12 +154,12 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
|
|||
|
||||
requestingAccount, _, err := f.GetRemoteAccount(ctx, username, publicKeyOwnerURI, false)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("couldn't get remote account: %s", err)
|
||||
return nil, false, fmt.Errorf("couldn't get requesting account %s: %s", publicKeyOwnerURI, err)
|
||||
}
|
||||
|
||||
withRequester := context.WithValue(ctx, util.APRequestingAccount, requestingAccount)
|
||||
withRequested := context.WithValue(withRequester, util.APAccount, requestedAccount)
|
||||
return withRequested, true, nil
|
||||
withRequesting := context.WithValue(ctx, util.APRequestingAccount, requestingAccount)
|
||||
withReceiving := context.WithValue(withRequesting, util.APReceivingAccount, receivingAccount)
|
||||
return withReceiving, true, nil
|
||||
}
|
||||
|
||||
// Blocked should determine whether to permit a set of actors given by
|
||||
|
@ -182,11 +182,11 @@ func (f *federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, er
|
|||
})
|
||||
l.Debugf("entering BLOCKED function with IRI list: %+v", actorIRIs)
|
||||
|
||||
requestedAccountI := ctx.Value(util.APAccount)
|
||||
requestedAccount, ok := requestedAccountI.(*gtsmodel.Account)
|
||||
receivingAccountI := ctx.Value(util.APReceivingAccount)
|
||||
receivingAccount, ok := receivingAccountI.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
f.log.Errorf("requested account not set on request context")
|
||||
return false, errors.New("requested account not set on request context, so couldn't determine blocks")
|
||||
f.log.Errorf("receiving account not set on request context")
|
||||
return false, errors.New("receiving account not set on request context, so couldn't determine blocks")
|
||||
}
|
||||
|
||||
blocked, err := f.db.AreURIsBlocked(ctx, actorIRIs)
|
||||
|
@ -209,12 +209,12 @@ func (f *federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, er
|
|||
return false, fmt.Errorf("error getting account with uri %s: %s", uri.String(), err)
|
||||
}
|
||||
|
||||
blocked, err = f.db.IsBlocked(ctx, requestedAccount.ID, requestingAccount.ID, false)
|
||||
blocked, err = f.db.IsBlocked(ctx, receivingAccount.ID, requestingAccount.ID, false)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error checking account block: %s", err)
|
||||
}
|
||||
if blocked {
|
||||
l.Tracef("local account %s blocks account with uri %s", requestedAccount.Username, uri)
|
||||
l.Tracef("local account %s blocks account with uri %s", receivingAccount.Username, uri)
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ func (suite *ProtocolTestSuite) TestAuthenticatePostInbox() {
|
|||
ctx := context.Background()
|
||||
// by the time AuthenticatePostInbox is called, PostInboxRequestBodyHook should have already been called,
|
||||
// which should have set the account and username onto the request. We can replicate that behavior here:
|
||||
ctxWithAccount := context.WithValue(ctx, util.APAccount, inboxAccount)
|
||||
ctxWithAccount := context.WithValue(ctx, util.APReceivingAccount, inboxAccount)
|
||||
ctxWithActivity := context.WithValue(ctxWithAccount, util.APActivity, activity)
|
||||
ctxWithVerifier := context.WithValue(ctxWithActivity, util.APRequestingPublicKeyVerifier, verifier)
|
||||
ctxWithSignature := context.WithValue(ctxWithVerifier, util.APRequestingPublicKeySignature, activity.SignatureHeader)
|
||||
|
|
|
@ -18,7 +18,11 @@
|
|||
|
||||
package messages
|
||||
|
||||
import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// FromClientAPI wraps a message that travels from the client API into the processor.
|
||||
type FromClientAPI struct {
|
||||
|
@ -31,8 +35,9 @@ type FromClientAPI struct {
|
|||
|
||||
// FromFederator wraps a message that travels from the federator into the processor.
|
||||
type FromFederator struct {
|
||||
APObjectType string
|
||||
APActivityType string
|
||||
GTSModel interface{}
|
||||
ReceivingAccount *gtsmodel.Account
|
||||
APObjectType string // what is the object type of this message? eg., Note, Profile etc.
|
||||
APActivityType string // what is the activity type of this message? eg., Create, Follow etc.
|
||||
APIri *url.URL // what is the IRI ID of this activity?
|
||||
GTSModel interface{} // representation of this object if it's already been converted into our internal gts model
|
||||
ReceivingAccount *gtsmodel.Account // which account owns the inbox that this activity was posted to?
|
||||
}
|
||||
|
|
|
@ -31,29 +31,89 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
)
|
||||
|
||||
// ProcessFromFederator reads the APActivityType and APObjectType of an incoming message from the federator,
|
||||
// and directs the message into the appropriate side effect handler function, or simply does nothing if there's
|
||||
// no handler function defined for the combination of Activity and Object.
|
||||
func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error {
|
||||
l := p.log.WithFields(logrus.Fields{
|
||||
"func": "processFromFederator",
|
||||
"federatorMsg": fmt.Sprintf("%+v", federatorMsg),
|
||||
"APActivityType": federatorMsg.APActivityType,
|
||||
"APObjectType": federatorMsg.APObjectType,
|
||||
})
|
||||
|
||||
l.Trace("entering function PROCESS FROM FEDERATOR")
|
||||
l.Trace("processing message from federator")
|
||||
|
||||
switch federatorMsg.APActivityType {
|
||||
case ap.ActivityCreate:
|
||||
// CREATE
|
||||
// CREATE SOMETHING
|
||||
switch federatorMsg.APObjectType {
|
||||
case ap.ObjectNote:
|
||||
// CREATE A STATUS
|
||||
incomingStatus, ok := federatorMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return errors.New("note was not parseable as *gtsmodel.Status")
|
||||
return p.processCreateStatusFromFederator(ctx, federatorMsg)
|
||||
case ap.ActivityLike:
|
||||
// CREATE A FAVE
|
||||
return p.processCreateFaveFromFederator(ctx, federatorMsg)
|
||||
case ap.ActivityFollow:
|
||||
// CREATE A FOLLOW REQUEST
|
||||
return p.processCreateFollowRequestFromFederator(ctx, federatorMsg)
|
||||
case ap.ActivityAnnounce:
|
||||
// CREATE AN ANNOUNCE
|
||||
return p.processCreateAnnounceFromFederator(ctx, federatorMsg)
|
||||
case ap.ActivityBlock:
|
||||
// CREATE A BLOCK
|
||||
return p.processCreateBlockFromFederator(ctx, federatorMsg)
|
||||
}
|
||||
case ap.ActivityUpdate:
|
||||
// UPDATE SOMETHING
|
||||
switch federatorMsg.APObjectType {
|
||||
case ap.ObjectProfile:
|
||||
// UPDATE AN ACCOUNT
|
||||
return p.processUpdateAccountFromFederator(ctx, federatorMsg)
|
||||
}
|
||||
case ap.ActivityDelete:
|
||||
// DELETE SOMETHING
|
||||
switch federatorMsg.APObjectType {
|
||||
case ap.ObjectNote:
|
||||
// DELETE A STATUS
|
||||
return p.processDeleteStatusFromFederator(ctx, federatorMsg)
|
||||
case ap.ObjectProfile:
|
||||
// DELETE A PROFILE/ACCOUNT
|
||||
return p.processDeleteAccountFromFederator(ctx, federatorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
status, err := p.federator.EnrichRemoteStatus(ctx, federatorMsg.ReceivingAccount.Username, incomingStatus, true)
|
||||
// not a combination we can/need to process
|
||||
return nil
|
||||
}
|
||||
|
||||
// processCreateStatusFromFederator handles Activity Create and Object Note
|
||||
func (p *processor) processCreateStatusFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error {
|
||||
// check for either an IRI that we still need to dereference, OR an already dereferenced
|
||||
// and converted status pinned to the message.
|
||||
var status *gtsmodel.Status
|
||||
|
||||
if federatorMsg.GTSModel != nil {
|
||||
// there's a gts model already pinned to the message, it should be a status
|
||||
var ok bool
|
||||
if status, ok = federatorMsg.GTSModel.(*gtsmodel.Status); !ok {
|
||||
return errors.New("ProcessFromFederator: note was not parseable as *gtsmodel.Status")
|
||||
}
|
||||
|
||||
var err error
|
||||
status, err = p.federator.EnrichRemoteStatus(ctx, federatorMsg.ReceivingAccount.Username, status, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// no model pinned, we need to dereference based on the IRI
|
||||
if federatorMsg.APIri == nil {
|
||||
return errors.New("ProcessFromFederator: status was not pinned to federatorMsg, and neither was an IRI for us to dereference")
|
||||
}
|
||||
var err error
|
||||
status, _, _, err = p.federator.GetRemoteStatus(ctx, federatorMsg.ReceivingAccount.Username, federatorMsg.APIri, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.timelineStatus(ctx, status); err != nil {
|
||||
return err
|
||||
|
@ -62,11 +122,12 @@ func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messa
|
|||
if err := p.notifyStatus(ctx, status); err != nil {
|
||||
return err
|
||||
}
|
||||
case ap.ObjectProfile:
|
||||
// CREATE AN ACCOUNT
|
||||
// nothing to do here
|
||||
case ap.ActivityLike:
|
||||
// CREATE A FAVE
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processCreateFaveFromFederator handles Activity Create and Object Like
|
||||
func (p *processor) processCreateFaveFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error {
|
||||
incomingFave, ok := federatorMsg.GTSModel.(*gtsmodel.StatusFave)
|
||||
if !ok {
|
||||
return errors.New("like was not parseable as *gtsmodel.StatusFave")
|
||||
|
@ -75,8 +136,12 @@ func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messa
|
|||
if err := p.notifyFave(ctx, incomingFave); err != nil {
|
||||
return err
|
||||
}
|
||||
case ap.ActivityFollow:
|
||||
// CREATE A FOLLOW REQUEST
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processCreateFollowRequestFromFederator handles Activity Create and Object Follow
|
||||
func (p *processor) processCreateFollowRequestFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error {
|
||||
followRequest, ok := federatorMsg.GTSModel.(*gtsmodel.FollowRequest)
|
||||
if !ok {
|
||||
return errors.New("incomingFollowRequest was not parseable as *gtsmodel.FollowRequest")
|
||||
|
@ -116,8 +181,10 @@ func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messa
|
|||
}
|
||||
|
||||
return p.notifyFollow(ctx, follow, targetAccount)
|
||||
case ap.ActivityAnnounce:
|
||||
// CREATE AN ANNOUNCE
|
||||
}
|
||||
|
||||
// processCreateAnnounceFromFederator handles Activity Create and Object Announce
|
||||
func (p *processor) processCreateAnnounceFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error {
|
||||
incomingAnnounce, ok := federatorMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return errors.New("announce was not parseable as *gtsmodel.Status")
|
||||
|
@ -144,8 +211,12 @@ func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messa
|
|||
if err := p.notifyAnnounce(ctx, incomingAnnounce); err != nil {
|
||||
return err
|
||||
}
|
||||
case ap.ActivityBlock:
|
||||
// CREATE A BLOCK
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processCreateBlockFromFederator handles Activity Create and Object Block
|
||||
func (p *processor) processCreateBlockFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error {
|
||||
block, ok := federatorMsg.GTSModel.(*gtsmodel.Block)
|
||||
if !ok {
|
||||
return errors.New("block was not parseable as *gtsmodel.Block")
|
||||
|
@ -160,12 +231,12 @@ func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messa
|
|||
}
|
||||
// TODO: same with notifications
|
||||
// TODO: same with bookmarks
|
||||
|
||||
return nil
|
||||
}
|
||||
case ap.ActivityUpdate:
|
||||
// UPDATE
|
||||
switch federatorMsg.APObjectType {
|
||||
case ap.ObjectProfile:
|
||||
// UPDATE AN ACCOUNT
|
||||
|
||||
// processUpdateAccountFromFederator handles Activity Update and Object Profile
|
||||
func (p *processor) processUpdateAccountFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error {
|
||||
incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
return errors.New("profile was not parseable as *gtsmodel.Account")
|
||||
|
@ -174,12 +245,12 @@ func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messa
|
|||
if _, err := p.federator.EnrichRemoteAccount(ctx, federatorMsg.ReceivingAccount.Username, incomingAccount); err != nil {
|
||||
return fmt.Errorf("error enriching updated account from federator: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
case ap.ActivityDelete:
|
||||
// DELETE
|
||||
switch federatorMsg.APObjectType {
|
||||
case ap.ObjectNote:
|
||||
// DELETE A STATUS
|
||||
|
||||
// processDeleteStatusFromFederator handles Activity Delete and Object Note
|
||||
func (p *processor) processDeleteStatusFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error {
|
||||
// TODO: handle side effects of status deletion here:
|
||||
// 1. delete all media associated with status
|
||||
// 2. delete boosts of status
|
||||
|
@ -210,9 +281,10 @@ func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messa
|
|||
|
||||
// remove this status from any and all timelines
|
||||
return p.deleteStatusFromTimelines(ctx, statusToDelete)
|
||||
case ap.ObjectProfile:
|
||||
// DELETE A PROFILE/ACCOUNT
|
||||
// handle side effects of account deletion here: delete all objects, statuses, media etc associated with account
|
||||
}
|
||||
|
||||
// processDeleteAccountFromFederator handles Activity Delete and Object Profile
|
||||
func (p *processor) processDeleteAccountFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error {
|
||||
account, ok := federatorMsg.GTSModel.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
return errors.New("account delete was not parseable as *gtsmodel.Account")
|
||||
|
@ -220,14 +292,3 @@ func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messa
|
|||
|
||||
return p.accountProcessor.Delete(ctx, account, account.ID)
|
||||
}
|
||||
case ap.ActivityAccept:
|
||||
// ACCEPT
|
||||
switch federatorMsg.APObjectType {
|
||||
case ap.ActivityFollow:
|
||||
// ACCEPT A FOLLOW
|
||||
// nothing to do here
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
type FromFederatorTestSuite struct {
|
||||
|
@ -486,6 +487,28 @@ func (suite *FromFederatorTestSuite) TestProcessFollowRequestUnlocked() {
|
|||
suite.Equal("Accept", accept.Type)
|
||||
}
|
||||
|
||||
// TestCreateStatusFromIRI checks if a forwarded status can be dereferenced by the processor.
|
||||
func (suite *FromFederatorTestSuite) TestCreateStatusFromIRI() {
|
||||
ctx := context.Background()
|
||||
|
||||
receivingAccount := suite.testAccounts["local_account_1"]
|
||||
statusCreator := suite.testAccounts["remote_account_2"]
|
||||
|
||||
err := suite.processor.ProcessFromFederator(ctx, messages.FromFederator{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: nil, // gtsmodel is nil because this is a forwarded status -- we want to dereference it using the iri
|
||||
ReceivingAccount: receivingAccount,
|
||||
APIri: testrig.URLMustParse("http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1"),
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
// status should now be in the database, attributed to remote_account_2
|
||||
s, err := suite.db.GetStatusByURI(context.Background(), "http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1")
|
||||
suite.NoError(err)
|
||||
suite.Equal(statusCreator.URI, s.AccountURI)
|
||||
}
|
||||
|
||||
func TestFromFederatorTestSuite(t *testing.T) {
|
||||
suite.Run(t, &FromFederatorTestSuite{})
|
||||
}
|
||||
|
|
|
@ -69,6 +69,7 @@ type ProcessingStandardTestSuite struct {
|
|||
testMentions map[string]*gtsmodel.Mention
|
||||
testAutheds map[string]*oauth.Auth
|
||||
testBlocks map[string]*gtsmodel.Block
|
||||
testActivities map[string]testrig.ActivityWithSignature
|
||||
|
||||
sentHTTPRequests map[string][]byte
|
||||
|
||||
|
@ -92,6 +93,7 @@ func (suite *ProcessingStandardTestSuite) SetupSuite() {
|
|||
Account: suite.testAccounts["local_account_1"],
|
||||
},
|
||||
}
|
||||
suite.testActivities = testrig.NewTestActivities(suite.testAccounts)
|
||||
suite.testBlocks = testrig.NewTestBlocks()
|
||||
}
|
||||
|
||||
|
@ -149,6 +151,32 @@ func (suite *ProcessingStandardTestSuite) SetupTest() {
|
|||
return response, nil
|
||||
}
|
||||
|
||||
if req.URL.String() == "http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1" {
|
||||
// the request is for the forwarded message
|
||||
message := suite.testActivities["forwarded_message"].Activity.GetActivityStreamsObject().At(0).GetActivityStreamsNote()
|
||||
messageI, err := streams.Serialize(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
messageJson, err := json.Marshal(messageI)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
responseType := "application/activity+json"
|
||||
|
||||
reader := bytes.NewReader(messageJson)
|
||||
readCloser := io.NopCloser(reader)
|
||||
response := &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: readCloser,
|
||||
ContentLength: int64(len(messageJson)),
|
||||
Header: http.Header{
|
||||
"content-type": {responseType},
|
||||
},
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
r := ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
|
|
|
@ -55,11 +55,11 @@ Text`
|
|||
|
||||
replaceMentionsWithLinkString = `Another test @foss_satan@fossbros-anonymous.io
|
||||
|
||||
https://fossbros-anonymous.io/@foss_satan/statuses/6675ee73-fccc-4562-a46a-3e8cd9798060`
|
||||
http://fossbros-anonymous.io/@foss_satan/statuses/6675ee73-fccc-4562-a46a-3e8cd9798060`
|
||||
|
||||
replaceMentionsWithLinkStringExpected = `Another test <span class="h-card"><a href="http://fossbros-anonymous.io/@foss_satan" class="u-url mention">@<span>foss_satan</span></a></span>
|
||||
|
||||
https://fossbros-anonymous.io/@foss_satan/statuses/6675ee73-fccc-4562-a46a-3e8cd9798060`
|
||||
http://fossbros-anonymous.io/@foss_satan/statuses/6675ee73-fccc-4562-a46a-3e8cd9798060`
|
||||
|
||||
replaceMentionsWithLinkSelfString = `Mentioning myself: @the_mighty_zork
|
||||
|
||||
|
|
|
@ -62,8 +62,8 @@ type APContextKey string
|
|||
const (
|
||||
// APActivity can be used to set and retrieve the actual go-fed pub.Activity within a context.
|
||||
APActivity APContextKey = "activity"
|
||||
// APAccount can be used the set and retrieve the account being interacted with
|
||||
APAccount APContextKey = "account"
|
||||
// APReceivingAccount can be used the set and retrieve the account being interacted with / receiving an activity in their inbox.
|
||||
APReceivingAccount APContextKey = "account"
|
||||
// APRequestingAccount can be used to set and retrieve the account of an incoming federation request.
|
||||
// This will often be the actor of the instance that's posting the request.
|
||||
APRequestingAccount APContextKey = "requestingAccount"
|
||||
|
|
|
@ -446,6 +446,41 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
|
|||
HideCollections: false,
|
||||
SuspensionOrigin: "",
|
||||
},
|
||||
"remote_account_2": {
|
||||
ID: "01FHMQX3GAABWSM0S2VZEC2SWC",
|
||||
Username: "some_user",
|
||||
Domain: "example.org",
|
||||
DisplayName: "some user",
|
||||
Fields: []gtsmodel.Field{},
|
||||
Note: "i'm a real son of a gun",
|
||||
Memorial: false,
|
||||
MovedToAccountID: "",
|
||||
CreatedAt: TimeMustParse("2020-08-10T14:13:28+02:00"),
|
||||
UpdatedAt: time.Now().Add(-1 * time.Hour),
|
||||
Bot: false,
|
||||
Locked: true,
|
||||
Discoverable: true,
|
||||
Sensitive: false,
|
||||
Language: "en",
|
||||
URI: "http://example.org/users/some_user",
|
||||
URL: "http://example.org/@some_user",
|
||||
LastWebfingeredAt: time.Time{},
|
||||
InboxURI: "http://example.org/users/some_user/inbox",
|
||||
OutboxURI: "http://example.org/users/some_user/outbox",
|
||||
FollowersURI: "http://example.org/users/some_user/followers",
|
||||
FollowingURI: "http://example.org/users/some_user/following",
|
||||
FeaturedCollectionURI: "http://example.org/users/some_user/collections/featured",
|
||||
ActorType: ap.ActorPerson,
|
||||
AlsoKnownAs: "",
|
||||
PrivateKey: &rsa.PrivateKey{},
|
||||
PublicKey: &rsa.PublicKey{},
|
||||
PublicKeyURI: "http://example.org/users/some_user#main-key",
|
||||
SensitizedAt: time.Time{},
|
||||
SilencedAt: time.Time{},
|
||||
SuspendedAt: time.Time{},
|
||||
HideCollections: false,
|
||||
SuspensionOrigin: "",
|
||||
},
|
||||
}
|
||||
|
||||
// generate keys for each account
|
||||
|
@ -1268,29 +1303,53 @@ type ActivityWithSignature struct {
|
|||
// their requesting signatures.
|
||||
func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]ActivityWithSignature {
|
||||
dmForZork := newNote(
|
||||
URLMustParse("https://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6"),
|
||||
URLMustParse("https://fossbros-anonymous.io/@foss_satan/5424b153-4553-4f30-9358-7b92f7cd42f6"),
|
||||
URLMustParse("http://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6"),
|
||||
URLMustParse("http://fossbros-anonymous.io/@foss_satan/5424b153-4553-4f30-9358-7b92f7cd42f6"),
|
||||
time.Now(),
|
||||
"hey zork here's a new private note for you",
|
||||
"new note for zork",
|
||||
URLMustParse("https://fossbros-anonymous.io/users/foss_satan"),
|
||||
URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
|
||||
[]*url.URL{URLMustParse("http://localhost:8080/users/the_mighty_zork")},
|
||||
nil,
|
||||
true,
|
||||
[]vocab.ActivityStreamsMention{})
|
||||
createDmForZork := wrapNoteInCreate(
|
||||
URLMustParse("https://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6/activity"),
|
||||
URLMustParse("https://fossbros-anonymous.io/users/foss_satan"),
|
||||
URLMustParse("http://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6/activity"),
|
||||
URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
|
||||
time.Now(),
|
||||
dmForZork)
|
||||
sig, digest, date := GetSignatureForActivity(createDmForZork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI))
|
||||
createDmForZorkSig, createDmForZorkDigest, creatDmForZorkDate := GetSignatureForActivity(createDmForZork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI))
|
||||
|
||||
forwardedMessage := newNote(
|
||||
URLMustParse("http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1"),
|
||||
URLMustParse("http://example.org/@some_user/afaba698-5740-4e32-a702-af61aa543bc1"),
|
||||
time.Now(),
|
||||
"this is a public status, please forward it!",
|
||||
"",
|
||||
URLMustParse("http://example.org/users/some_user"),
|
||||
[]*url.URL{URLMustParse(pub.PublicActivityPubIRI)},
|
||||
nil,
|
||||
false,
|
||||
[]vocab.ActivityStreamsMention{})
|
||||
createForwardedMessage := wrapNoteInCreate(
|
||||
URLMustParse("http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1/activity"),
|
||||
URLMustParse("http://example.org/users/some_user"),
|
||||
time.Now(),
|
||||
forwardedMessage)
|
||||
createForwardedMessageSig, createForwardedMessageDigest, createForwardedMessageDate := GetSignatureForActivity(createForwardedMessage, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI))
|
||||
|
||||
return map[string]ActivityWithSignature{
|
||||
"dm_for_zork": {
|
||||
Activity: createDmForZork,
|
||||
SignatureHeader: sig,
|
||||
DigestHeader: digest,
|
||||
DateHeader: date,
|
||||
SignatureHeader: createDmForZorkSig,
|
||||
DigestHeader: createDmForZorkDigest,
|
||||
DateHeader: creatDmForZorkDate,
|
||||
},
|
||||
"forwarded_message": {
|
||||
Activity: createForwardedMessage,
|
||||
SignatureHeader: createForwardedMessageSig,
|
||||
DigestHeader: createForwardedMessageDigest,
|
||||
DateHeader: createForwardedMessageDate,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue