mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2024-11-23 12:53:23 +00:00
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
18 changed files with 765 additions and 383 deletions
|
@ -48,12 +48,9 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
||||||
l.Debug("entering Accept")
|
l.Debug("entering Accept")
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
receivingAccount, _, fromFederatorChan := extractFromCtx(ctx)
|
||||||
if err != nil {
|
if receivingAccount == nil || fromFederatorChan == nil {
|
||||||
return err
|
// If the receiving account or federator channel wasn't set on the context, that means this request didn't pass
|
||||||
}
|
|
||||||
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,
|
// 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.
|
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
|
||||||
return nil
|
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
|
// 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")
|
return errors.New("ACCEPT: follow object account and inbox account were not the same")
|
||||||
}
|
}
|
||||||
follow, err := f.db.AcceptFollowRequest(ctx, gtsFollowRequest.AccountID, gtsFollowRequest.TargetAccountID)
|
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,
|
APObjectType: ap.ActivityFollow,
|
||||||
APActivityType: ap.ActivityAccept,
|
APActivityType: ap.ActivityAccept,
|
||||||
GTSModel: follow,
|
GTSModel: follow,
|
||||||
ReceivingAccount: targetAcct,
|
ReceivingAccount: receivingAccount,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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)
|
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
|
// 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")
|
return errors.New("ACCEPT: follow object account and inbox account were not the same")
|
||||||
}
|
}
|
||||||
follow, err := f.db.AcceptFollowRequest(ctx, gtsFollow.AccountID, gtsFollow.TargetAccountID)
|
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,
|
APObjectType: ap.ActivityFollow,
|
||||||
APActivityType: ap.ActivityAccept,
|
APActivityType: ap.ActivityAccept,
|
||||||
GTSModel: follow,
|
GTSModel: follow,
|
||||||
ReceivingAccount: targetAcct,
|
ReceivingAccount: receivingAccount,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -44,12 +44,9 @@ func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStre
|
||||||
l.Debug("entering Announce")
|
l.Debug("entering Announce")
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
receivingAccount, _, fromFederatorChan := extractFromCtx(ctx)
|
||||||
if err != nil {
|
if receivingAccount == nil || fromFederatorChan == nil {
|
||||||
return err
|
// If the receiving account or federator channel wasn't set on the context, that means this request didn't pass
|
||||||
}
|
|
||||||
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,
|
// 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.
|
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
|
||||||
return nil
|
return nil
|
||||||
|
@ -57,7 +54,7 @@ func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStre
|
||||||
|
|
||||||
boost, isNew, err := f.typeConverter.ASAnnounceToStatus(ctx, announce)
|
boost, isNew, err := f.typeConverter.ASAnnounceToStatus(ctx, announce)
|
||||||
if err != nil {
|
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 {
|
if !isNew {
|
||||||
|
@ -70,7 +67,7 @@ func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStre
|
||||||
APObjectType: ap.ActivityAnnounce,
|
APObjectType: ap.ActivityAnnounce,
|
||||||
APActivityType: ap.ActivityCreate,
|
APActivityType: ap.ActivityCreate,
|
||||||
GTSModel: boost,
|
GTSModel: boost,
|
||||||
ReceivingAccount: targetAcct,
|
ReceivingAccount: receivingAccount,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -22,11 +22,13 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-fed/activity/streams/vocab"
|
"github.com/go-fed/activity/streams/vocab"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||||
)
|
)
|
||||||
|
@ -59,144 +61,261 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
||||||
l.Debug("entering Create")
|
l.Debug("entering Create")
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
receivingAccount, requestingAccount, fromFederatorChan := extractFromCtx(ctx)
|
||||||
if err != nil {
|
if receivingAccount == nil || fromFederatorChan == nil {
|
||||||
return err
|
// If the receiving account or federator channel wasn't set on the context, that means this request didn't pass
|
||||||
}
|
|
||||||
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,
|
// 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.
|
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch asType.GetTypeName() {
|
switch asType.GetTypeName() {
|
||||||
case ap.ActivityCreate:
|
|
||||||
// CREATE SOMETHING
|
|
||||||
create, ok := asType.(vocab.ActivityStreamsCreate)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("CREATE: could not convert type to create")
|
|
||||||
}
|
|
||||||
object := create.GetActivityStreamsObject()
|
|
||||||
for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() {
|
|
||||||
switch objectIter.GetType().GetTypeName() {
|
|
||||||
case ap.ObjectNote:
|
|
||||||
// CREATE A NOTE
|
|
||||||
note := objectIter.GetActivityStreamsNote()
|
|
||||||
status, err := f.typeConverter.ASStatusToStatus(ctx, note)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("CREATE: error converting note to status: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// id the status based on the time it was created
|
|
||||||
statusID, err := id.NewULIDFromTime(status.CreatedAt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
status.ID = statusID
|
|
||||||
|
|
||||||
if err := f.db.PutStatus(ctx, status); err != nil {
|
|
||||||
if err == db.ErrAlreadyExists {
|
|
||||||
// the status already exists in the database, which means we've already handled everything else,
|
|
||||||
// so we can just return nil here and be done with it.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// an actual error has happened
|
|
||||||
return fmt.Errorf("CREATE: database error inserting status: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fromFederatorChan <- messages.FromFederator{
|
|
||||||
APObjectType: ap.ObjectNote,
|
|
||||||
APActivityType: ap.ActivityCreate,
|
|
||||||
GTSModel: status,
|
|
||||||
ReceivingAccount: targetAcct,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ap.ActivityFollow:
|
|
||||||
// FOLLOW SOMETHING
|
|
||||||
follow, ok := asType.(vocab.ActivityStreamsFollow)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("CREATE: 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
newID, err := id.NewULID()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
followRequest.ID = newID
|
|
||||||
|
|
||||||
if err := f.db.Put(ctx, followRequest); err != nil {
|
|
||||||
return fmt.Errorf("CREATE: database error inserting follow request: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fromFederatorChan <- messages.FromFederator{
|
|
||||||
APObjectType: ap.ActivityFollow,
|
|
||||||
APActivityType: ap.ActivityCreate,
|
|
||||||
GTSModel: followRequest,
|
|
||||||
ReceivingAccount: targetAcct,
|
|
||||||
}
|
|
||||||
case ap.ActivityLike:
|
|
||||||
// LIKE SOMETHING
|
|
||||||
like, ok := asType.(vocab.ActivityStreamsLike)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("CREATE: 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
newID, err := id.NewULID()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fave.ID = newID
|
|
||||||
|
|
||||||
if err := f.db.Put(ctx, fave); err != nil {
|
|
||||||
return fmt.Errorf("CREATE: database error inserting fave: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fromFederatorChan <- messages.FromFederator{
|
|
||||||
APObjectType: ap.ActivityLike,
|
|
||||||
APActivityType: ap.ActivityCreate,
|
|
||||||
GTSModel: fave,
|
|
||||||
ReceivingAccount: targetAcct,
|
|
||||||
}
|
|
||||||
case ap.ActivityBlock:
|
case ap.ActivityBlock:
|
||||||
// BLOCK SOMETHING
|
// BLOCK SOMETHING
|
||||||
blockable, ok := asType.(vocab.ActivityStreamsBlock)
|
return f.activityBlock(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan)
|
||||||
if !ok {
|
case ap.ActivityCreate:
|
||||||
return errors.New("CREATE: could not convert type to block")
|
// CREATE SOMETHING
|
||||||
}
|
return f.activityCreate(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan)
|
||||||
|
case ap.ActivityFollow:
|
||||||
block, err := f.typeConverter.ASBlockToBlock(ctx, blockable)
|
// FOLLOW SOMETHING
|
||||||
if err != nil {
|
return f.activityFollow(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan)
|
||||||
return fmt.Errorf("CREATE: could not convert Block to gts model block")
|
case ap.ActivityLike:
|
||||||
}
|
// LIKE SOMETHING
|
||||||
|
return f.activityLike(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan)
|
||||||
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
|
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("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() {
|
||||||
|
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
|
||||||
|
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("createNote: error converting note to status: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// id the status based on the time it was created
|
||||||
|
statusID, err := id.NewULIDFromTime(status.CreatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
status.ID = statusID
|
||||||
|
|
||||||
|
if err := f.db.PutStatus(ctx, status); err != nil {
|
||||||
|
if err == db.ErrAlreadyExists {
|
||||||
|
// the status already exists in the database, which means we've already handled everything else,
|
||||||
|
// so we can just return nil here and be done with it.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// an actual error has happened
|
||||||
|
return fmt.Errorf("createNote: database error inserting status: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fromFederatorChan <- messages.FromFederator{
|
||||||
|
APObjectType: ap.ObjectNote,
|
||||||
|
APActivityType: ap.ActivityCreate,
|
||||||
|
GTSModel: status,
|
||||||
|
ReceivingAccount: receivingAccount,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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("activityFollow: could not convert type to follow")
|
||||||
|
}
|
||||||
|
|
||||||
|
followRequest, err := f.typeConverter.ASFollowToFollowRequest(ctx, follow)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("activityFollow: could not convert Follow to follow request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newID, err := id.NewULID()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
followRequest.ID = newID
|
||||||
|
|
||||||
|
if err := f.db.Put(ctx, followRequest); err != nil {
|
||||||
|
return fmt.Errorf("activityFollow: database error inserting follow request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fromFederatorChan <- messages.FromFederator{
|
||||||
|
APObjectType: ap.ActivityFollow,
|
||||||
|
APActivityType: ap.ActivityCreate,
|
||||||
|
GTSModel: followRequest,
|
||||||
|
ReceivingAccount: receivingAccount,
|
||||||
|
}
|
||||||
|
|
||||||
|
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("activityLike: could not convert type to like")
|
||||||
|
}
|
||||||
|
|
||||||
|
fave, err := f.typeConverter.ASLikeToFave(ctx, like)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("activityLike: could not convert Like to fave: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newID, err := id.NewULID()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fave.ID = newID
|
||||||
|
|
||||||
|
if err := f.db.Put(ctx, fave); err != nil {
|
||||||
|
return fmt.Errorf("activityLike: database error inserting fave: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fromFederatorChan <- messages.FromFederator{
|
||||||
|
APObjectType: ap.ActivityLike,
|
||||||
|
APActivityType: ap.ActivityCreate,
|
||||||
|
GTSModel: fave,
|
||||||
|
ReceivingAccount: receivingAccount,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
91
internal/federation/federatingdb/create_test.go
Normal file
91
internal/federation/federatingdb/create_test.go
Normal file
|
@ -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")
|
l.Debug("entering Delete")
|
||||||
|
|
||||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
receivingAccount, _, fromFederatorChan := extractFromCtx(ctx)
|
||||||
if err != nil {
|
if receivingAccount == nil || fromFederatorChan == nil {
|
||||||
return err
|
// If the receiving account or federator channel wasn't set on the context, that means this request didn't pass
|
||||||
}
|
|
||||||
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,
|
// 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.
|
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
|
||||||
return nil
|
return nil
|
||||||
|
@ -68,7 +65,7 @@ func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error {
|
||||||
APObjectType: ap.ObjectNote,
|
APObjectType: ap.ObjectNote,
|
||||||
APActivityType: ap.ActivityDelete,
|
APActivityType: ap.ActivityDelete,
|
||||||
GTSModel: s,
|
GTSModel: s,
|
||||||
ReceivingAccount: targetAcct,
|
ReceivingAccount: receivingAccount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +77,7 @@ func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error {
|
||||||
APObjectType: ap.ObjectProfile,
|
APObjectType: ap.ObjectProfile,
|
||||||
APActivityType: ap.ActivityDelete,
|
APActivityType: ap.ActivityDelete,
|
||||||
GTSModel: a,
|
GTSModel: a,
|
||||||
ReceivingAccount: targetAcct,
|
ReceivingAccount: receivingAccount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,17 @@
|
||||||
package federatingdb_test
|
package federatingdb_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb"
|
"github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,6 +49,7 @@ type FederatingDBTestSuite struct {
|
||||||
testAttachments map[string]*gtsmodel.MediaAttachment
|
testAttachments map[string]*gtsmodel.MediaAttachment
|
||||||
testStatuses map[string]*gtsmodel.Status
|
testStatuses map[string]*gtsmodel.Status
|
||||||
testBlocks map[string]*gtsmodel.Block
|
testBlocks map[string]*gtsmodel.Block
|
||||||
|
testActivities map[string]testrig.ActivityWithSignature
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FederatingDBTestSuite) SetupSuite() {
|
func (suite *FederatingDBTestSuite) SetupSuite() {
|
||||||
|
@ -56,6 +61,7 @@ func (suite *FederatingDBTestSuite) SetupSuite() {
|
||||||
suite.testAttachments = testrig.NewTestAttachments()
|
suite.testAttachments = testrig.NewTestAttachments()
|
||||||
suite.testStatuses = testrig.NewTestStatuses()
|
suite.testStatuses = testrig.NewTestStatuses()
|
||||||
suite.testBlocks = testrig.NewTestBlocks()
|
suite.testBlocks = testrig.NewTestBlocks()
|
||||||
|
suite.testActivities = testrig.NewTestActivities(suite.testAccounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FederatingDBTestSuite) SetupTest() {
|
func (suite *FederatingDBTestSuite) SetupTest() {
|
||||||
|
@ -70,3 +76,11 @@ func (suite *FederatingDBTestSuite) SetupTest() {
|
||||||
func (suite *FederatingDBTestSuite) TearDownTest() {
|
func (suite *FederatingDBTestSuite) TearDownTest() {
|
||||||
testrig.StandardDBTeardown(suite.db)
|
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")
|
l.Debug("entering Undo")
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
receivingAccount, _, fromFederatorChan := extractFromCtx(ctx)
|
||||||
if err != nil {
|
if receivingAccount == nil || fromFederatorChan == nil {
|
||||||
return err
|
// If the receiving account or federator channel wasn't set on the context, that means this request didn't pass
|
||||||
}
|
|
||||||
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,
|
// 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.
|
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
|
||||||
return nil
|
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)
|
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
|
// 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")
|
return errors.New("UNDO: follow object account and inbox account were not the same")
|
||||||
}
|
}
|
||||||
// delete any existing FOLLOW
|
// 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)
|
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
|
// 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")
|
return errors.New("UNDO: block object account and inbox account were not the same")
|
||||||
}
|
}
|
||||||
// delete any existing BLOCK
|
// delete any existing BLOCK
|
||||||
|
|
|
@ -56,12 +56,9 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
||||||
l.Debug("entering Update")
|
l.Debug("entering Update")
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
receivingAccount, _, fromFederatorChan := extractFromCtx(ctx)
|
||||||
if err != nil {
|
if receivingAccount == nil || fromFederatorChan == nil {
|
||||||
return err
|
// If the receiving account or federator channel wasn't set on the context, that means this request didn't pass
|
||||||
}
|
|
||||||
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,
|
// 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.
|
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
|
||||||
return nil
|
return nil
|
||||||
|
@ -153,7 +150,7 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
||||||
APObjectType: ap.ObjectProfile,
|
APObjectType: ap.ObjectProfile,
|
||||||
APActivityType: ap.ActivityUpdate,
|
APActivityType: ap.ActivityUpdate,
|
||||||
GTSModel: updatedAcct,
|
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:
|
// 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 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.
|
// - A channel that messages for the processor can be placed into.
|
||||||
func extractFromCtx(ctx context.Context) (*gtsmodel.Account, chan messages.FromFederator, error) {
|
// If a value is not present, nil will be returned for it. It's up to the caller to check this and respond appropriately.
|
||||||
var targetAcct *gtsmodel.Account
|
func extractFromCtx(ctx context.Context) (receivingAccount, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) {
|
||||||
targetAcctI := ctx.Value(util.APAccount)
|
receivingAccountI := ctx.Value(util.APReceivingAccount)
|
||||||
if targetAcctI != nil {
|
if receivingAccountI != nil {
|
||||||
var ok bool
|
receivingAccount = receivingAccountI.(*gtsmodel.Account)
|
||||||
targetAcct, ok = targetAcctI.(*gtsmodel.Account)
|
}
|
||||||
if !ok {
|
|
||||||
return nil, nil, errors.New("extractFromCtx: account value in context not parseable")
|
requestingAcctI := ctx.Value(util.APRequestingAccount)
|
||||||
}
|
if requestingAcctI != nil {
|
||||||
|
requestingAccount = requestingAcctI.(*gtsmodel.Account)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fromFederatorChan chan messages.FromFederator
|
|
||||||
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
|
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
|
||||||
if fromFederatorChanI != nil {
|
if fromFederatorChanI != nil {
|
||||||
var ok bool
|
fromFederatorChan = fromFederatorChanI.(chan messages.FromFederator)
|
||||||
fromFederatorChan, ok = fromFederatorChanI.(chan messages.FromFederator)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, errors.New("extractFromCtx: fromFederatorChan value in context not parseable")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetAcct, fromFederatorChan, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalItem(item vocab.Type) (string, error) {
|
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")
|
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 {
|
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 {
|
if err != nil {
|
||||||
l.Debugf("request not authenticated: %s", err)
|
l.Debugf("request not authenticated: %s", err)
|
||||||
return ctx, false, 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)
|
requestingAccount, _, err := f.GetRemoteAccount(ctx, username, publicKeyOwnerURI, false)
|
||||||
if err != nil {
|
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)
|
withRequesting := context.WithValue(ctx, util.APRequestingAccount, requestingAccount)
|
||||||
withRequested := context.WithValue(withRequester, util.APAccount, requestedAccount)
|
withReceiving := context.WithValue(withRequesting, util.APReceivingAccount, receivingAccount)
|
||||||
return withRequested, true, nil
|
return withReceiving, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocked should determine whether to permit a set of actors given by
|
// 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)
|
l.Debugf("entering BLOCKED function with IRI list: %+v", actorIRIs)
|
||||||
|
|
||||||
requestedAccountI := ctx.Value(util.APAccount)
|
receivingAccountI := ctx.Value(util.APReceivingAccount)
|
||||||
requestedAccount, ok := requestedAccountI.(*gtsmodel.Account)
|
receivingAccount, ok := receivingAccountI.(*gtsmodel.Account)
|
||||||
if !ok {
|
if !ok {
|
||||||
f.log.Errorf("requested account not set on request context")
|
f.log.Errorf("receiving account not set on request context")
|
||||||
return false, errors.New("requested account not set on request context, so couldn't determine blocks")
|
return false, errors.New("receiving account not set on request context, so couldn't determine blocks")
|
||||||
}
|
}
|
||||||
|
|
||||||
blocked, err := f.db.AreURIsBlocked(ctx, actorIRIs)
|
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)
|
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 {
|
if err != nil {
|
||||||
return false, fmt.Errorf("error checking account block: %s", err)
|
return false, fmt.Errorf("error checking account block: %s", err)
|
||||||
}
|
}
|
||||||
if blocked {
|
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
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@ func (suite *ProtocolTestSuite) TestAuthenticatePostInbox() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
// by the time AuthenticatePostInbox is called, PostInboxRequestBodyHook should have already been called,
|
// 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:
|
// 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)
|
ctxWithActivity := context.WithValue(ctxWithAccount, util.APActivity, activity)
|
||||||
ctxWithVerifier := context.WithValue(ctxWithActivity, util.APRequestingPublicKeyVerifier, verifier)
|
ctxWithVerifier := context.WithValue(ctxWithActivity, util.APRequestingPublicKeyVerifier, verifier)
|
||||||
ctxWithSignature := context.WithValue(ctxWithVerifier, util.APRequestingPublicKeySignature, activity.SignatureHeader)
|
ctxWithSignature := context.WithValue(ctxWithVerifier, util.APRequestingPublicKeySignature, activity.SignatureHeader)
|
||||||
|
|
|
@ -18,7 +18,11 @@
|
||||||
|
|
||||||
package messages
|
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.
|
// FromClientAPI wraps a message that travels from the client API into the processor.
|
||||||
type FromClientAPI struct {
|
type FromClientAPI struct {
|
||||||
|
@ -31,8 +35,9 @@ type FromClientAPI struct {
|
||||||
|
|
||||||
// FromFederator wraps a message that travels from the federator into the processor.
|
// FromFederator wraps a message that travels from the federator into the processor.
|
||||||
type FromFederator struct {
|
type FromFederator struct {
|
||||||
APObjectType string
|
APObjectType string // what is the object type of this message? eg., Note, Profile etc.
|
||||||
APActivityType string
|
APActivityType string // what is the activity type of this message? eg., Create, Follow etc.
|
||||||
GTSModel interface{}
|
APIri *url.URL // what is the IRI ID of this activity?
|
||||||
ReceivingAccount *gtsmodel.Account
|
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,203 +31,264 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
"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 {
|
func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error {
|
||||||
l := p.log.WithFields(logrus.Fields{
|
l := p.log.WithFields(logrus.Fields{
|
||||||
"func": "processFromFederator",
|
"func": "processFromFederator",
|
||||||
"federatorMsg": fmt.Sprintf("%+v", federatorMsg),
|
"APActivityType": federatorMsg.APActivityType,
|
||||||
|
"APObjectType": federatorMsg.APObjectType,
|
||||||
})
|
})
|
||||||
|
l.Trace("processing message from federator")
|
||||||
l.Trace("entering function PROCESS FROM FEDERATOR")
|
|
||||||
|
|
||||||
switch federatorMsg.APActivityType {
|
switch federatorMsg.APActivityType {
|
||||||
case ap.ActivityCreate:
|
case ap.ActivityCreate:
|
||||||
// CREATE
|
// CREATE SOMETHING
|
||||||
switch federatorMsg.APObjectType {
|
switch federatorMsg.APObjectType {
|
||||||
case ap.ObjectNote:
|
case ap.ObjectNote:
|
||||||
// CREATE A STATUS
|
// CREATE A STATUS
|
||||||
incomingStatus, ok := federatorMsg.GTSModel.(*gtsmodel.Status)
|
return p.processCreateStatusFromFederator(ctx, federatorMsg)
|
||||||
if !ok {
|
|
||||||
return errors.New("note was not parseable as *gtsmodel.Status")
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := p.federator.EnrichRemoteStatus(ctx, federatorMsg.ReceivingAccount.Username, incomingStatus, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.timelineStatus(ctx, status); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.notifyStatus(ctx, status); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case ap.ObjectProfile:
|
|
||||||
// CREATE AN ACCOUNT
|
|
||||||
// nothing to do here
|
|
||||||
case ap.ActivityLike:
|
case ap.ActivityLike:
|
||||||
// CREATE A FAVE
|
// CREATE A FAVE
|
||||||
incomingFave, ok := federatorMsg.GTSModel.(*gtsmodel.StatusFave)
|
return p.processCreateFaveFromFederator(ctx, federatorMsg)
|
||||||
if !ok {
|
|
||||||
return errors.New("like was not parseable as *gtsmodel.StatusFave")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.notifyFave(ctx, incomingFave); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case ap.ActivityFollow:
|
case ap.ActivityFollow:
|
||||||
// CREATE A FOLLOW REQUEST
|
// CREATE A FOLLOW REQUEST
|
||||||
followRequest, ok := federatorMsg.GTSModel.(*gtsmodel.FollowRequest)
|
return p.processCreateFollowRequestFromFederator(ctx, federatorMsg)
|
||||||
if !ok {
|
|
||||||
return errors.New("incomingFollowRequest was not parseable as *gtsmodel.FollowRequest")
|
|
||||||
}
|
|
||||||
|
|
||||||
if followRequest.TargetAccount == nil {
|
|
||||||
a, err := p.db.GetAccountByID(ctx, followRequest.TargetAccountID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
followRequest.TargetAccount = a
|
|
||||||
}
|
|
||||||
targetAccount := followRequest.TargetAccount
|
|
||||||
|
|
||||||
if targetAccount.Locked {
|
|
||||||
// if the account is locked just notify the follow request and nothing else
|
|
||||||
return p.notifyFollowRequest(ctx, followRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
if followRequest.Account == nil {
|
|
||||||
a, err := p.db.GetAccountByID(ctx, followRequest.AccountID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
followRequest.Account = a
|
|
||||||
}
|
|
||||||
originAccount := followRequest.Account
|
|
||||||
|
|
||||||
// if the target account isn't locked, we should already accept the follow and notify about the new follower instead
|
|
||||||
follow, err := p.db.AcceptFollowRequest(ctx, followRequest.AccountID, followRequest.TargetAccountID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.federateAcceptFollowRequest(ctx, follow, originAccount, targetAccount); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.notifyFollow(ctx, follow, targetAccount)
|
|
||||||
case ap.ActivityAnnounce:
|
case ap.ActivityAnnounce:
|
||||||
// CREATE AN ANNOUNCE
|
// CREATE AN ANNOUNCE
|
||||||
incomingAnnounce, ok := federatorMsg.GTSModel.(*gtsmodel.Status)
|
return p.processCreateAnnounceFromFederator(ctx, federatorMsg)
|
||||||
if !ok {
|
|
||||||
return errors.New("announce was not parseable as *gtsmodel.Status")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.federator.DereferenceAnnounce(ctx, incomingAnnounce, federatorMsg.ReceivingAccount.Username); err != nil {
|
|
||||||
return fmt.Errorf("error dereferencing announce from federator: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
incomingAnnounceID, err := id.NewULIDFromTime(incomingAnnounce.CreatedAt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
incomingAnnounce.ID = incomingAnnounceID
|
|
||||||
|
|
||||||
if err := p.db.PutStatus(ctx, incomingAnnounce); err != nil {
|
|
||||||
return fmt.Errorf("error adding dereferenced announce to the db: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.timelineStatus(ctx, incomingAnnounce); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.notifyAnnounce(ctx, incomingAnnounce); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case ap.ActivityBlock:
|
case ap.ActivityBlock:
|
||||||
// CREATE A BLOCK
|
// CREATE A BLOCK
|
||||||
block, ok := federatorMsg.GTSModel.(*gtsmodel.Block)
|
return p.processCreateBlockFromFederator(ctx, federatorMsg)
|
||||||
if !ok {
|
|
||||||
return errors.New("block was not parseable as *gtsmodel.Block")
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove any of the blocking account's statuses from the blocked account's timeline, and vice versa
|
|
||||||
if err := p.timelineManager.WipeStatusesFromAccountID(ctx, block.AccountID, block.TargetAccountID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := p.timelineManager.WipeStatusesFromAccountID(ctx, block.TargetAccountID, block.AccountID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// TODO: same with notifications
|
|
||||||
// TODO: same with bookmarks
|
|
||||||
}
|
}
|
||||||
case ap.ActivityUpdate:
|
case ap.ActivityUpdate:
|
||||||
// UPDATE
|
// UPDATE SOMETHING
|
||||||
switch federatorMsg.APObjectType {
|
switch federatorMsg.APObjectType {
|
||||||
case ap.ObjectProfile:
|
case ap.ObjectProfile:
|
||||||
// UPDATE AN ACCOUNT
|
// UPDATE AN ACCOUNT
|
||||||
incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account)
|
return p.processUpdateAccountFromFederator(ctx, federatorMsg)
|
||||||
if !ok {
|
|
||||||
return errors.New("profile was not parseable as *gtsmodel.Account")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := p.federator.EnrichRemoteAccount(ctx, federatorMsg.ReceivingAccount.Username, incomingAccount); err != nil {
|
|
||||||
return fmt.Errorf("error enriching updated account from federator: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case ap.ActivityDelete:
|
case ap.ActivityDelete:
|
||||||
// DELETE
|
// DELETE SOMETHING
|
||||||
switch federatorMsg.APObjectType {
|
switch federatorMsg.APObjectType {
|
||||||
case ap.ObjectNote:
|
case ap.ObjectNote:
|
||||||
// DELETE A STATUS
|
// DELETE A STATUS
|
||||||
// TODO: handle side effects of status deletion here:
|
return p.processDeleteStatusFromFederator(ctx, federatorMsg)
|
||||||
// 1. delete all media associated with status
|
|
||||||
// 2. delete boosts of status
|
|
||||||
// 3. etc etc etc
|
|
||||||
statusToDelete, ok := federatorMsg.GTSModel.(*gtsmodel.Status)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("note was not parseable as *gtsmodel.Status")
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete all attachments for this status
|
|
||||||
for _, a := range statusToDelete.AttachmentIDs {
|
|
||||||
if err := p.mediaProcessor.Delete(ctx, a); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete all mentions for this status
|
|
||||||
for _, m := range statusToDelete.MentionIDs {
|
|
||||||
if err := p.db.DeleteByID(ctx, m, >smodel.Mention{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete all notifications for this status
|
|
||||||
if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "status_id", Value: statusToDelete.ID}}, &[]*gtsmodel.Notification{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove this status from any and all timelines
|
|
||||||
return p.deleteStatusFromTimelines(ctx, statusToDelete)
|
|
||||||
case ap.ObjectProfile:
|
case ap.ObjectProfile:
|
||||||
// DELETE A PROFILE/ACCOUNT
|
// DELETE A PROFILE/ACCOUNT
|
||||||
// handle side effects of account deletion here: delete all objects, statuses, media etc associated with account
|
return p.processDeleteAccountFromFederator(ctx, federatorMsg)
|
||||||
account, ok := federatorMsg.GTSModel.(*gtsmodel.Account)
|
}
|
||||||
if !ok {
|
}
|
||||||
return errors.New("account delete was not parseable as *gtsmodel.Account")
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.accountProcessor.Delete(ctx, account, account.ID)
|
// 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")
|
||||||
}
|
}
|
||||||
case ap.ActivityAccept:
|
|
||||||
// ACCEPT
|
var err error
|
||||||
switch federatorMsg.APObjectType {
|
status, err = p.federator.EnrichRemoteStatus(ctx, federatorMsg.ReceivingAccount.Username, status, true)
|
||||||
case ap.ActivityFollow:
|
if err != nil {
|
||||||
// ACCEPT A FOLLOW
|
return err
|
||||||
// nothing to do here
|
|
||||||
}
|
}
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.notifyStatus(ctx, status); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.notifyFave(ctx, incomingFave); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
if followRequest.TargetAccount == nil {
|
||||||
|
a, err := p.db.GetAccountByID(ctx, followRequest.TargetAccountID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
followRequest.TargetAccount = a
|
||||||
|
}
|
||||||
|
targetAccount := followRequest.TargetAccount
|
||||||
|
|
||||||
|
if targetAccount.Locked {
|
||||||
|
// if the account is locked just notify the follow request and nothing else
|
||||||
|
return p.notifyFollowRequest(ctx, followRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if followRequest.Account == nil {
|
||||||
|
a, err := p.db.GetAccountByID(ctx, followRequest.AccountID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
followRequest.Account = a
|
||||||
|
}
|
||||||
|
originAccount := followRequest.Account
|
||||||
|
|
||||||
|
// if the target account isn't locked, we should already accept the follow and notify about the new follower instead
|
||||||
|
follow, err := p.db.AcceptFollowRequest(ctx, followRequest.AccountID, followRequest.TargetAccountID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.federateAcceptFollowRequest(ctx, follow, originAccount, targetAccount); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.notifyFollow(ctx, follow, targetAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.federator.DereferenceAnnounce(ctx, incomingAnnounce, federatorMsg.ReceivingAccount.Username); err != nil {
|
||||||
|
return fmt.Errorf("error dereferencing announce from federator: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
incomingAnnounceID, err := id.NewULIDFromTime(incomingAnnounce.CreatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
incomingAnnounce.ID = incomingAnnounceID
|
||||||
|
|
||||||
|
if err := p.db.PutStatus(ctx, incomingAnnounce); err != nil {
|
||||||
|
return fmt.Errorf("error adding dereferenced announce to the db: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.timelineStatus(ctx, incomingAnnounce); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.notifyAnnounce(ctx, incomingAnnounce); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove any of the blocking account's statuses from the blocked account's timeline, and vice versa
|
||||||
|
if err := p.timelineManager.WipeStatusesFromAccountID(ctx, block.AccountID, block.TargetAccountID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.timelineManager.WipeStatusesFromAccountID(ctx, block.TargetAccountID, block.AccountID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// TODO: same with notifications
|
||||||
|
// TODO: same with bookmarks
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// 3. etc etc etc
|
||||||
|
statusToDelete, ok := federatorMsg.GTSModel.(*gtsmodel.Status)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("note was not parseable as *gtsmodel.Status")
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete all attachments for this status
|
||||||
|
for _, a := range statusToDelete.AttachmentIDs {
|
||||||
|
if err := p.mediaProcessor.Delete(ctx, a); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete all mentions for this status
|
||||||
|
for _, m := range statusToDelete.MentionIDs {
|
||||||
|
if err := p.db.DeleteByID(ctx, m, >smodel.Mention{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete all notifications for this status
|
||||||
|
if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "status_id", Value: statusToDelete.ID}}, &[]*gtsmodel.Notification{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove this status from any and all timelines
|
||||||
|
return p.deleteStatusFromTimelines(ctx, statusToDelete)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.accountProcessor.Delete(ctx, account, account.ID)
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FromFederatorTestSuite struct {
|
type FromFederatorTestSuite struct {
|
||||||
|
@ -486,6 +487,28 @@ func (suite *FromFederatorTestSuite) TestProcessFollowRequestUnlocked() {
|
||||||
suite.Equal("Accept", accept.Type)
|
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) {
|
func TestFromFederatorTestSuite(t *testing.T) {
|
||||||
suite.Run(t, &FromFederatorTestSuite{})
|
suite.Run(t, &FromFederatorTestSuite{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,7 @@ type ProcessingStandardTestSuite struct {
|
||||||
testMentions map[string]*gtsmodel.Mention
|
testMentions map[string]*gtsmodel.Mention
|
||||||
testAutheds map[string]*oauth.Auth
|
testAutheds map[string]*oauth.Auth
|
||||||
testBlocks map[string]*gtsmodel.Block
|
testBlocks map[string]*gtsmodel.Block
|
||||||
|
testActivities map[string]testrig.ActivityWithSignature
|
||||||
|
|
||||||
sentHTTPRequests map[string][]byte
|
sentHTTPRequests map[string][]byte
|
||||||
|
|
||||||
|
@ -92,6 +93,7 @@ func (suite *ProcessingStandardTestSuite) SetupSuite() {
|
||||||
Account: suite.testAccounts["local_account_1"],
|
Account: suite.testAccounts["local_account_1"],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
suite.testActivities = testrig.NewTestActivities(suite.testAccounts)
|
||||||
suite.testBlocks = testrig.NewTestBlocks()
|
suite.testBlocks = testrig.NewTestBlocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,6 +151,32 @@ func (suite *ProcessingStandardTestSuite) SetupTest() {
|
||||||
return response, nil
|
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{}))
|
r := ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
|
|
|
@ -55,11 +55,11 @@ Text`
|
||||||
|
|
||||||
replaceMentionsWithLinkString = `Another test @foss_satan@fossbros-anonymous.io
|
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>
|
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
|
replaceMentionsWithLinkSelfString = `Mentioning myself: @the_mighty_zork
|
||||||
|
|
||||||
|
|
|
@ -62,8 +62,8 @@ type APContextKey string
|
||||||
const (
|
const (
|
||||||
// APActivity can be used to set and retrieve the actual go-fed pub.Activity within a context.
|
// APActivity can be used to set and retrieve the actual go-fed pub.Activity within a context.
|
||||||
APActivity APContextKey = "activity"
|
APActivity APContextKey = "activity"
|
||||||
// APAccount can be used the set and retrieve the account being interacted with
|
// APReceivingAccount can be used the set and retrieve the account being interacted with / receiving an activity in their inbox.
|
||||||
APAccount APContextKey = "account"
|
APReceivingAccount APContextKey = "account"
|
||||||
// APRequestingAccount can be used to set and retrieve the account of an incoming federation request.
|
// 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.
|
// This will often be the actor of the instance that's posting the request.
|
||||||
APRequestingAccount APContextKey = "requestingAccount"
|
APRequestingAccount APContextKey = "requestingAccount"
|
||||||
|
|
|
@ -446,6 +446,41 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
|
||||||
HideCollections: false,
|
HideCollections: false,
|
||||||
SuspensionOrigin: "",
|
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
|
// generate keys for each account
|
||||||
|
@ -1268,29 +1303,53 @@ type ActivityWithSignature struct {
|
||||||
// their requesting signatures.
|
// their requesting signatures.
|
||||||
func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]ActivityWithSignature {
|
func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]ActivityWithSignature {
|
||||||
dmForZork := newNote(
|
dmForZork := newNote(
|
||||||
URLMustParse("https://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6"),
|
URLMustParse("http://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/@foss_satan/5424b153-4553-4f30-9358-7b92f7cd42f6"),
|
||||||
time.Now(),
|
time.Now(),
|
||||||
"hey zork here's a new private note for you",
|
"hey zork here's a new private note for you",
|
||||||
"new note for zork",
|
"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")},
|
[]*url.URL{URLMustParse("http://localhost:8080/users/the_mighty_zork")},
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
[]vocab.ActivityStreamsMention{})
|
[]vocab.ActivityStreamsMention{})
|
||||||
createDmForZork := wrapNoteInCreate(
|
createDmForZork := wrapNoteInCreate(
|
||||||
URLMustParse("https://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6/activity"),
|
URLMustParse("http://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"),
|
||||||
time.Now(),
|
time.Now(),
|
||||||
dmForZork)
|
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{
|
return map[string]ActivityWithSignature{
|
||||||
"dm_for_zork": {
|
"dm_for_zork": {
|
||||||
Activity: createDmForZork,
|
Activity: createDmForZork,
|
||||||
SignatureHeader: sig,
|
SignatureHeader: createDmForZorkSig,
|
||||||
DigestHeader: digest,
|
DigestHeader: createDmForZorkDigest,
|
||||||
DateHeader: date,
|
DateHeader: creatDmForZorkDate,
|
||||||
|
},
|
||||||
|
"forwarded_message": {
|
||||||
|
Activity: createForwardedMessage,
|
||||||
|
SignatureHeader: createForwardedMessageSig,
|
||||||
|
DigestHeader: createForwardedMessageDigest,
|
||||||
|
DateHeader: createForwardedMessageDate,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue