diff options
| author | 2023-04-05 20:10:05 +0200 | |
|---|---|---|
| committer | 2023-04-05 20:10:05 +0200 | |
| commit | 8d2a76c58ce018fb6cd6760a3607cf1ee720037a (patch) | |
| tree | 5443b6a9828483c884150639b9cdee02bc850319 /internal/federation/federatingdb | |
| parent | [chore] Update templates license headers (#1672) (diff) | |
| download | gotosocial-8d2a76c58ce018fb6cd6760a3607cf1ee720037a.tar.xz | |
[bugfix] Add proper constraints on status faves, dedupe (#1674)
* [bugfix] Start working on multiple like issue
* finish up
Diffstat (limited to 'internal/federation/federatingdb')
| -rw-r--r-- | internal/federation/federatingdb/create.go | 12 | ||||
| -rw-r--r-- | internal/federation/federatingdb/owns.go | 66 | ||||
| -rw-r--r-- | internal/federation/federatingdb/undo.go | 200 | ||||
| -rw-r--r-- | internal/federation/federatingdb/util.go | 18 | 
4 files changed, 210 insertions, 86 deletions
| diff --git a/internal/federation/federatingdb/create.go b/internal/federation/federatingdb/create.go index 166ea34a9..b14a0597b 100644 --- a/internal/federation/federatingdb/create.go +++ b/internal/federation/federatingdb/create.go @@ -288,13 +288,19 @@ func (f *federatingDB) activityLike(ctx context.Context, asType vocab.Type, rece  	fave, err := f.typeConverter.ASLikeToFave(ctx, like)  	if err != nil { -		return fmt.Errorf("activityLike: could not convert Like to fave: %s", err) +		return fmt.Errorf("activityLike: could not convert Like to fave: %w", err)  	}  	fave.ID = id.NewULID() -	if err := f.state.DB.Put(ctx, fave); err != nil { -		return fmt.Errorf("activityLike: database error inserting fave: %s", err) +	if err := f.state.DB.PutStatusFave(ctx, fave); err != nil { +		if errors.Is(err, db.ErrAlreadyExists) { +			// The Like already exists in the database, which +			// means we've already handled side effects. We can +			// just return nil here and be done with it. +			return nil +		} +		return fmt.Errorf("activityLike: database error inserting fave: %w", err)  	}  	f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{ diff --git a/internal/federation/federatingdb/owns.go b/internal/federation/federatingdb/owns.go index 62cb7a3ea..cc3bb7d5d 100644 --- a/internal/federation/federatingdb/owns.go +++ b/internal/federation/federatingdb/owns.go @@ -19,12 +19,14 @@ package federatingdb  import (  	"context" +	"errors"  	"fmt"  	"net/url"  	"codeberg.org/gruf/go-kv"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/gtscontext"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/log"  	"github.com/superseriousbusiness/gotosocial/internal/uris" @@ -46,6 +48,11 @@ func (f *federatingDB) Owns(ctx context.Context, id *url.URL) (bool, error) {  		return false, nil  	} +	// todo: refactor the below; make sure we use +	// proper db functions for everything, and +	// preferably clean up by calling subfuncs +	// (like we now do for ownsLike). +  	// apparently it belongs to this host, so what *is* it?  	// check if it's a status, eg /users/example_username/statuses/SOME_UUID_OF_A_STATUS  	if uris.IsStatusesPath(id) { @@ -117,28 +124,7 @@ func (f *federatingDB) Owns(ctx context.Context, id *url.URL) (bool, error) {  	}  	if uris.IsLikePath(id) { -		username, likeID, err := uris.ParseLikedPath(id) -		if err != nil { -			return false, fmt.Errorf("error parsing like path for url %s: %s", id.String(), err) -		} -		if _, err := f.state.DB.GetAccountByUsernameDomain(ctx, username, ""); err != nil { -			if err == db.ErrNoEntries { -				// there are no entries for this username -				return false, nil -			} -			// an actual error happened -			return false, fmt.Errorf("database error fetching account with username %s: %s", username, err) -		} -		if err := f.state.DB.GetByID(ctx, likeID, >smodel.StatusFave{}); err != nil { -			if err == db.ErrNoEntries { -				// there are no entries -				return false, nil -			} -			// an actual error happened -			return false, fmt.Errorf("database error fetching like with id %s: %s", likeID, err) -		} -		l.Debugf("we own url %s", id.String()) -		return true, nil +		return f.ownsLike(ctx, id)  	}  	if uris.IsBlockPath(id) { @@ -168,3 +154,39 @@ func (f *federatingDB) Owns(ctx context.Context, id *url.URL) (bool, error) {  	return false, fmt.Errorf("could not match activityID: %s", id.String())  } + +func (f *federatingDB) ownsLike(ctx context.Context, uri *url.URL) (bool, error) { +	username, id, err := uris.ParseLikedPath(uri) +	if err != nil { +		return false, fmt.Errorf("error parsing Like path for url %s: %w", uri.String(), err) +	} + +	// We're only checking for existence, +	// so use barebones context. +	bbCtx := gtscontext.SetBarebones(ctx) + +	if _, err := f.state.DB.GetAccountByUsernameDomain(bbCtx, username, ""); err != nil { +		if errors.Is(err, db.ErrNoEntries) { +			// No entries for this acct, +			// we don't own this item. +			return false, nil +		} + +		// Actual error. +		return false, fmt.Errorf("database error fetching account with username %s: %w", username, err) +	} + +	if _, err := f.state.DB.GetStatusFaveByID(bbCtx, id); err != nil { +		if errors.Is(err, db.ErrNoEntries) { +			// No entries for this ID, +			// we don't own this item. +			return false, nil +		} + +		// Actual error. +		return false, fmt.Errorf("database error fetching status fave with id %s: %w", id, err) +	} + +	log.Tracef(ctx, "we own Like %s", uri.String()) +	return true, nil +} diff --git a/internal/federation/federatingdb/undo.go b/internal/federation/federatingdb/undo.go index 517aa9cc6..c17bd2e90 100644 --- a/internal/federation/federatingdb/undo.go +++ b/internal/federation/federatingdb/undo.go @@ -26,11 +26,13 @@ import (  	"github.com/superseriousbusiness/activity/streams/vocab"  	"github.com/superseriousbusiness/gotosocial/internal/ap"  	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/log"  )  func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) error { -	l := log.Entry{}.WithContext(ctx) +	l := log.WithContext(ctx)  	if log.Level() >= level.DEBUG {  		i, err := marshalItem(undo) @@ -55,70 +57,160 @@ func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo)  	}  	for iter := undoObject.Begin(); iter != undoObject.End(); iter = iter.Next() { -		if iter.GetType() == nil { +		t := iter.GetType() +		if t == nil {  			continue  		} -		switch iter.GetType().GetTypeName() { + +		switch t.GetTypeName() {  		case ap.ActivityFollow: -			// UNDO FOLLOW -			ASFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow) -			if !ok { -				return errors.New("UNDO: couldn't parse follow into vocab.ActivityStreamsFollow") -			} -			// make sure the actor owns the follow -			if !sameActor(undo.GetActivityStreamsActor(), ASFollow.GetActivityStreamsActor()) { -				return errors.New("UNDO: follow actor and activity actor not the same") -			} -			// convert the follow to something we can understand -			gtsFollow, err := f.typeConverter.ASFollowToFollow(ctx, ASFollow) -			if err != nil { -				return fmt.Errorf("UNDO: error converting asfollow to gtsfollow: %s", err) -			} -			// make sure the addressee of the original follow is the same as whatever inbox this landed in -			if gtsFollow.TargetAccountID != receivingAccount.ID { -				return errors.New("UNDO: follow object account and inbox account were not the same") +			if err := f.undoFollow(ctx, receivingAccount, undo, t); err != nil { +				return err  			} -			// delete any existing FOLLOW -			if err := f.state.DB.DeleteFollowByURI(ctx, gtsFollow.URI); err != nil && !errors.Is(err, db.ErrNoEntries) { -				return fmt.Errorf("UNDO: db error removing follow: %s", err) -			} -			// delete any existing FOLLOW REQUEST -			if err := f.state.DB.DeleteFollowRequestByURI(ctx, gtsFollow.URI); err != nil && !errors.Is(err, db.ErrNoEntries) { -				return fmt.Errorf("UNDO: db error removing follow request: %s", err) -			} -			l.Debug("follow undone") -			return nil  		case ap.ActivityLike: -			// UNDO LIKE +			if err := f.undoLike(ctx, receivingAccount, undo, t); err != nil { +				return err +			}  		case ap.ActivityAnnounce: -			// UNDO BOOST/REBLOG/ANNOUNCE +			// todo: undo boost / reblog / announce  		case ap.ActivityBlock: -			// UNDO BLOCK -			ASBlock, ok := iter.GetType().(vocab.ActivityStreamsBlock) -			if !ok { -				return errors.New("UNDO: couldn't parse block into vocab.ActivityStreamsBlock") +			if err := f.undoBlock(ctx, receivingAccount, undo, t); err != nil { +				return err  			} -			// make sure the actor owns the follow -			if !sameActor(undo.GetActivityStreamsActor(), ASBlock.GetActivityStreamsActor()) { -				return errors.New("UNDO: block actor and activity actor not the same") -			} -			// convert the block to something we can understand -			gtsBlock, err := f.typeConverter.ASBlockToBlock(ctx, ASBlock) -			if err != nil { -				return fmt.Errorf("UNDO: error converting asblock to gtsblock: %s", err) -			} -			// make sure the addressee of the original block is the same as whatever inbox this landed in -			if gtsBlock.TargetAccountID != receivingAccount.ID { -				return errors.New("UNDO: block object account and inbox account were not the same") -			} -			// delete any existing BLOCK -			if err := f.state.DB.DeleteBlockByURI(ctx, gtsBlock.URI); err != nil { -				return fmt.Errorf("UNDO: db error removing block: %s", err) -			} -			l.Debug("block undone") +		} +	} + +	return nil +} + +func (f *federatingDB) undoFollow( +	ctx context.Context, +	receivingAccount *gtsmodel.Account, +	undo vocab.ActivityStreamsUndo, +	t vocab.Type, +) error { +	Follow, ok := t.(vocab.ActivityStreamsFollow) +	if !ok { +		return errors.New("undoFollow: couldn't parse vocab.Type into vocab.ActivityStreamsFollow") +	} + +	// Make sure the undo actor owns the target. +	if !sameActor(undo.GetActivityStreamsActor(), Follow.GetActivityStreamsActor()) { +		// Ignore this Activity. +		return nil +	} + +	follow, err := f.typeConverter.ASFollowToFollow(ctx, Follow) +	if err != nil { +		return fmt.Errorf("undoFollow: error converting ActivityStreams Follow to follow: %w", err) +	} + +	// Ensure addressee is follow target. +	if follow.TargetAccountID != receivingAccount.ID { +		// Ignore this Activity. +		return nil +	} + +	// Delete any existing follow with this URI. +	if err := f.state.DB.DeleteFollowByURI(ctx, follow.URI); err != nil && !errors.Is(err, db.ErrNoEntries) { +		return fmt.Errorf("undoFollow: db error removing follow: %w", err) +	} + +	// Delete any existing follow request with this URI. +	if err := f.state.DB.DeleteFollowRequestByURI(ctx, follow.URI); err != nil && !errors.Is(err, db.ErrNoEntries) { +		return fmt.Errorf("undoFollow: db error removing follow request: %w", err) +	} + +	log.Debug(ctx, "Follow undone") +	return nil +} + +func (f *federatingDB) undoLike( +	ctx context.Context, +	receivingAccount *gtsmodel.Account, +	undo vocab.ActivityStreamsUndo, +	t vocab.Type, +) error { +	Like, ok := t.(vocab.ActivityStreamsLike) +	if !ok { +		return errors.New("undoLike: couldn't parse vocab.Type into vocab.ActivityStreamsLike") +	} + +	// Make sure the undo actor owns the target. +	if !sameActor(undo.GetActivityStreamsActor(), Like.GetActivityStreamsActor()) { +		// Ignore this Activity. +		return nil +	} + +	fave, err := f.typeConverter.ASLikeToFave(ctx, Like) +	if err != nil { +		return fmt.Errorf("undoLike: error converting ActivityStreams Like to fave: %w", err) +	} + +	// Ensure addressee is fave target. +	if fave.TargetAccountID != receivingAccount.ID { +		// Ignore this Activity. +		return nil +	} + +	// Ignore URI on Likes, since we often get multiple Likes +	// with the same target and account ID, but differing URIs. +	// Instead, we'll select using account and target status. +	// Regardless of the URI, we can read an Undo Like to mean +	// "I don't want to fave this post anymore". +	fave, err = f.state.DB.GetStatusFave(gtscontext.SetBarebones(ctx), fave.AccountID, fave.StatusID) +	if err != nil { +		if errors.Is(err, db.ErrNoEntries) { +			// We didn't have a like/fave +			// for this combo anyway, ignore.  			return nil  		} +		// Real error. +		return fmt.Errorf("undoLike: db error getting fave from %s targeting %s: %w", fave.AccountID, fave.StatusID, err) +	} + +	// Delete the status fave. +	if err := f.state.DB.DeleteStatusFaveByID(ctx, fave.ID); err != nil { +		return fmt.Errorf("undoLike: db error deleting fave %s: %w", fave.ID, err) +	} + +	log.Debug(ctx, "Like undone") +	return nil +} + +func (f *federatingDB) undoBlock( +	ctx context.Context, +	receivingAccount *gtsmodel.Account, +	undo vocab.ActivityStreamsUndo, +	t vocab.Type, +) error { +	Block, ok := t.(vocab.ActivityStreamsBlock) +	if !ok { +		return errors.New("undoBlock: couldn't parse vocab.Type into vocab.ActivityStreamsBlock") +	} + +	// Make sure the undo actor owns the target. +	if !sameActor(undo.GetActivityStreamsActor(), Block.GetActivityStreamsActor()) { +		// Ignore this Activity. +		return nil +	} + +	block, err := f.typeConverter.ASBlockToBlock(ctx, Block) +	if err != nil { +		return fmt.Errorf("undoBlock: error converting ActivityStreams Block to block: %w", err) +	} + +	// Ensure addressee is block target. +	if block.TargetAccountID != receivingAccount.ID { +		// Ignore this Activity. +		return nil +	} + +	// Delete any existing BLOCK +	if err := f.state.DB.DeleteBlockByURI(ctx, block.URI); err != nil && !errors.Is(err, db.ErrNoEntries) { +		return fmt.Errorf("undoBlock: db error removing block: %w", err)  	} +	log.Debug(ctx, "Block undone")  	return nil  } diff --git a/internal/federation/federatingdb/util.go b/internal/federation/federatingdb/util.go index 8ad209f5e..5137145f2 100644 --- a/internal/federation/federatingdb/util.go +++ b/internal/federation/federatingdb/util.go @@ -36,23 +36,27 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/uris"  ) -func sameActor(activityActor vocab.ActivityStreamsActorProperty, followActor vocab.ActivityStreamsActorProperty) bool { -	if activityActor == nil || followActor == nil { +func sameActor(actor1 vocab.ActivityStreamsActorProperty, actor2 vocab.ActivityStreamsActorProperty) bool { +	if actor1 == nil || actor2 == nil {  		return false  	} -	for aIter := activityActor.Begin(); aIter != activityActor.End(); aIter = aIter.Next() { -		for fIter := followActor.Begin(); fIter != followActor.End(); fIter = fIter.Next() { -			if aIter.GetIRI() == nil { + +	for a1Iter := actor1.Begin(); a1Iter != actor1.End(); a1Iter = a1Iter.Next() { +		for a2Iter := actor2.Begin(); a2Iter != actor2.End(); a2Iter = a2Iter.Next() { +			if a1Iter.GetIRI() == nil {  				return false  			} -			if fIter.GetIRI() == nil { + +			if a2Iter.GetIRI() == nil {  				return false  			} -			if aIter.GetIRI().String() == fIter.GetIRI().String() { + +			if a1Iter.GetIRI().String() == a2Iter.GetIRI().String() {  				return true  			}  		}  	} +  	return false  } | 
