mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 04:12:25 -05: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,33 +61,166 @@ 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.ActivityBlock: | ||||||
|  | 		// BLOCK SOMETHING | ||||||
|  | 		return f.activityBlock(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan) | ||||||
| 	case ap.ActivityCreate: | 	case ap.ActivityCreate: | ||||||
| 		// CREATE SOMETHING | 		// 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) | 	create, ok := asType.(vocab.ActivityStreamsCreate) | ||||||
| 	if !ok { | 	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() | 	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() { | 	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: | 		case ap.ObjectNote: | ||||||
| 			// CREATE A NOTE | 			// 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) | 	status, err := f.typeConverter.ASStatusToStatus(ctx, note) | ||||||
| 	if err != nil { | 	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 | 	// 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 | 			return nil | ||||||
| 		} | 		} | ||||||
| 		// an actual error has happened | 		// 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{ | 	fromFederatorChan <- messages.FromFederator{ | ||||||
| 		APObjectType:     ap.ObjectNote, | 		APObjectType:     ap.ObjectNote, | ||||||
| 		APActivityType:   ap.ActivityCreate, | 		APActivityType:   ap.ActivityCreate, | ||||||
| 		GTSModel:         status, | 		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) | 	follow, ok := asType.(vocab.ActivityStreamsFollow) | ||||||
| 	if !ok { | 	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) | 	followRequest, err := f.typeConverter.ASFollowToFollowRequest(ctx, follow) | ||||||
| 	if err != nil { | 	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() | 	newID, err := id.NewULID() | ||||||
|  | @ -132,25 +272,32 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { | ||||||
| 	followRequest.ID = newID | 	followRequest.ID = newID | ||||||
| 
 | 
 | ||||||
| 	if err := f.db.Put(ctx, followRequest); err != nil { | 	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{ | 	fromFederatorChan <- messages.FromFederator{ | ||||||
| 		APObjectType:     ap.ActivityFollow, | 		APObjectType:     ap.ActivityFollow, | ||||||
| 		APActivityType:   ap.ActivityCreate, | 		APActivityType:   ap.ActivityCreate, | ||||||
| 		GTSModel:         followRequest, | 		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) | 	like, ok := asType.(vocab.ActivityStreamsLike) | ||||||
| 	if !ok { | 	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) | 	fave, err := f.typeConverter.ASLikeToFave(ctx, like) | ||||||
| 	if err != nil { | 	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() | 	newID, err := id.NewULID() | ||||||
|  | @ -160,43 +307,15 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { | ||||||
| 	fave.ID = newID | 	fave.ID = newID | ||||||
| 
 | 
 | ||||||
| 	if err := f.db.Put(ctx, fave); err != nil { | 	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{ | 	fromFederatorChan <- messages.FromFederator{ | ||||||
| 		APObjectType:     ap.ActivityLike, | 		APObjectType:     ap.ActivityLike, | ||||||
| 		APActivityType:   ap.ActivityCreate, | 		APActivityType:   ap.ActivityCreate, | ||||||
| 		GTSModel:         fave, | 		GTSModel:         fave, | ||||||
| 			ReceivingAccount: targetAcct, | 		ReceivingAccount: receivingAccount, | ||||||
| 		} |  | ||||||
| 	case ap.ActivityBlock: |  | ||||||
| 		// BLOCK SOMETHING |  | ||||||
| 		blockable, ok := asType.(vocab.ActivityStreamsBlock) |  | ||||||
| 		if !ok { |  | ||||||
| 			return errors.New("CREATE: could not convert type to block") |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		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 | 	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,29 +31,89 @@ 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 { | 		case ap.ActivityLike: | ||||||
| 				return errors.New("note was not parseable as *gtsmodel.Status") | 			// 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 { | 		if err != nil { | ||||||
| 			return err | 			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 { | 	if err := p.timelineStatus(ctx, status); err != nil { | ||||||
| 		return err | 		return err | ||||||
|  | @ -62,11 +122,12 @@ func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messa | ||||||
| 	if err := p.notifyStatus(ctx, status); err != nil { | 	if err := p.notifyStatus(ctx, status); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 		case ap.ObjectProfile: | 
 | ||||||
| 			// CREATE AN ACCOUNT | 	return nil | ||||||
| 			// nothing to do here | } | ||||||
| 		case ap.ActivityLike: | 
 | ||||||
| 			// CREATE A FAVE | // processCreateFaveFromFederator handles Activity Create and Object Like | ||||||
|  | func (p *processor) processCreateFaveFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { | ||||||
| 	incomingFave, ok := federatorMsg.GTSModel.(*gtsmodel.StatusFave) | 	incomingFave, ok := federatorMsg.GTSModel.(*gtsmodel.StatusFave) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("like was not parseable as *gtsmodel.StatusFave") | 		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 { | 	if err := p.notifyFave(ctx, incomingFave); err != nil { | ||||||
| 		return err | 		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) | 	followRequest, ok := federatorMsg.GTSModel.(*gtsmodel.FollowRequest) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("incomingFollowRequest was not parseable as *gtsmodel.FollowRequest") | 		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) | 	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) | 	incomingAnnounce, ok := federatorMsg.GTSModel.(*gtsmodel.Status) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("announce was not parseable as *gtsmodel.Status") | 		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 { | 	if err := p.notifyAnnounce(ctx, incomingAnnounce); err != nil { | ||||||
| 		return err | 		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) | 	block, ok := federatorMsg.GTSModel.(*gtsmodel.Block) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("block was not parseable as *gtsmodel.Block") | 		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 notifications | ||||||
| 	// TODO: same with bookmarks | 	// TODO: same with bookmarks | ||||||
| 		} | 
 | ||||||
| 	case ap.ActivityUpdate: | 	return nil | ||||||
| 		// UPDATE | } | ||||||
| 		switch federatorMsg.APObjectType { | 
 | ||||||
| 		case ap.ObjectProfile: | // processUpdateAccountFromFederator handles Activity Update and Object Profile | ||||||
| 			// UPDATE AN ACCOUNT | func (p *processor) processUpdateAccountFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { | ||||||
| 	incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account) | 	incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("profile was not parseable as *gtsmodel.Account") | 		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 { | 	if _, err := p.federator.EnrichRemoteAccount(ctx, federatorMsg.ReceivingAccount.Username, incomingAccount); err != nil { | ||||||
| 		return fmt.Errorf("error enriching updated account from federator: %s", err) | 		return fmt.Errorf("error enriching updated account from federator: %s", err) | ||||||
| 	} | 	} | ||||||
| 		} | 
 | ||||||
| 	case ap.ActivityDelete: | 	return nil | ||||||
| 		// DELETE | } | ||||||
| 		switch federatorMsg.APObjectType { | 
 | ||||||
| 		case ap.ObjectNote: | // processDeleteStatusFromFederator handles Activity Delete and Object Note | ||||||
| 			// DELETE A STATUS | func (p *processor) processDeleteStatusFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { | ||||||
| 	// TODO: handle side effects of status deletion here: | 	// TODO: handle side effects of status deletion here: | ||||||
| 	// 1. delete all media associated with status | 	// 1. delete all media associated with status | ||||||
| 	// 2. delete boosts of status | 	// 2. delete boosts of status | ||||||
|  | @ -210,24 +281,14 @@ func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messa | ||||||
| 
 | 
 | ||||||
| 	// remove this status from any and all timelines | 	// remove this status from any and all timelines | ||||||
| 	return p.deleteStatusFromTimelines(ctx, statusToDelete) | 	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) | 	account, ok := federatorMsg.GTSModel.(*gtsmodel.Account) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("account delete was not parseable as *gtsmodel.Account") | 		return errors.New("account delete was not parseable as *gtsmodel.Account") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return p.accountProcessor.Delete(ctx, account, account.ID) | 	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/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…
	
	Add table
		Add a link
		
	
		Reference in a new issue