[chore] Refactor federatingDB.Undo, avoid 500 errors on Undo Like (#3310)
This commit is contained in:
parent
71261c62c2
commit
0567b319c6
|
@ -168,16 +168,24 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr
|
||||||
// the POST request is authentic (properly signed) and authorized
|
// the POST request is authentic (properly signed) and authorized
|
||||||
// (permitted to interact with the target inbox).
|
// (permitted to interact with the target inbox).
|
||||||
//
|
//
|
||||||
// Post the activity to the Actor's inbox and trigger side effects .
|
// Post the activity to the Actor's inbox and trigger side effects.
|
||||||
if err := f.sideEffectActor.PostInbox(ctx, inboxID, activity); err != nil {
|
if err := f.sideEffectActor.PostInbox(ctx, inboxID, activity); err != nil {
|
||||||
// Special case: We know it is a bad request if the object or target
|
// Check if a function in the federatingDB
|
||||||
// props needed to be populated, or we failed parsing activity details.
|
// has returned an explicit errWithCode for us.
|
||||||
// Send the rejection to the peer.
|
if errWithCode, ok := err.(gtserror.WithCode); ok {
|
||||||
if errors.Is(err, pub.ErrObjectRequired) ||
|
return false, errWithCode
|
||||||
errors.Is(err, pub.ErrTargetRequired) ||
|
}
|
||||||
gtserror.IsMalformed(err) {
|
|
||||||
|
// Check if it's a bad request because the
|
||||||
|
// object or target props weren't populated,
|
||||||
|
// or we failed parsing activity details.
|
||||||
|
//
|
||||||
|
// Log such activities to help debug, then
|
||||||
|
// return the rejection (400) to the peer.
|
||||||
|
if gtserror.IsMalformed(err) ||
|
||||||
|
errors.Is(err, pub.ErrObjectRequired) ||
|
||||||
|
errors.Is(err, pub.ErrTargetRequired) {
|
||||||
|
|
||||||
// Log malformed activities to help debug.
|
|
||||||
l = l.WithField("activity", activity)
|
l = l.WithField("activity", activity)
|
||||||
l.Warnf("malformed incoming activity: %v", err)
|
l.Warnf("malformed incoming activity: %v", err)
|
||||||
|
|
||||||
|
@ -185,7 +193,7 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr
|
||||||
return false, gtserror.NewErrorBadRequest(errors.New(text), text)
|
return false, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// There's been some real error.
|
// Default: there's been some real error.
|
||||||
err := gtserror.Newf("error calling sideEffectActor.PostInbox: %w", err)
|
err := gtserror.Newf("error calling sideEffectActor.PostInbox: %w", err)
|
||||||
return false, gtserror.NewErrorInternalError(err)
|
return false, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,14 +33,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) error {
|
func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) error {
|
||||||
l := log.WithContext(ctx)
|
|
||||||
|
|
||||||
if log.Level() >= level.DEBUG {
|
if log.Level() >= level.DEBUG {
|
||||||
i, err := marshalItem(undo)
|
i, err := marshalItem(undo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
l = l.WithField("undo", i)
|
l := log.WithContext(ctx).
|
||||||
|
WithField("undo", i)
|
||||||
l.Debug("entering Undo")
|
l.Debug("entering Undo")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,80 +51,129 @@ func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo)
|
||||||
requestingAcct := activityContext.requestingAcct
|
requestingAcct := activityContext.requestingAcct
|
||||||
receivingAcct := activityContext.receivingAcct
|
receivingAcct := activityContext.receivingAcct
|
||||||
|
|
||||||
var errs gtserror.MultiError
|
|
||||||
for _, object := range ap.ExtractObjects(undo) {
|
for _, object := range ap.ExtractObjects(undo) {
|
||||||
// Try to get object as vocab.Type,
|
// Try to get object as vocab.Type,
|
||||||
// else skip handling (likely) IRI.
|
// else skip handling (likely) IRI.
|
||||||
objType := object.GetType()
|
asType := object.GetType()
|
||||||
if objType == nil {
|
if asType == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch objType.GetTypeName() {
|
// Check and handle any
|
||||||
|
// vocab.Type objects.
|
||||||
|
switch asType.GetTypeName() {
|
||||||
|
|
||||||
|
// UNDO FOLLOW
|
||||||
case ap.ActivityFollow:
|
case ap.ActivityFollow:
|
||||||
if err := f.undoFollow(ctx, receivingAcct, requestingAcct, undo, objType); err != nil {
|
if err := f.undoFollow(
|
||||||
errs.Appendf("error undoing follow: %w", err)
|
ctx,
|
||||||
|
receivingAcct,
|
||||||
|
requestingAcct,
|
||||||
|
undo,
|
||||||
|
asType,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UNDO LIKE
|
||||||
case ap.ActivityLike:
|
case ap.ActivityLike:
|
||||||
if err := f.undoLike(ctx, receivingAcct, requestingAcct, undo, objType); err != nil {
|
if err := f.undoLike(
|
||||||
errs.Appendf("error undoing like: %w", err)
|
ctx,
|
||||||
|
receivingAcct,
|
||||||
|
requestingAcct,
|
||||||
|
undo,
|
||||||
|
asType,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
case ap.ActivityAnnounce:
|
|
||||||
// TODO: actually handle this !
|
// UNDO BLOCK
|
||||||
log.Warn(ctx, "skipped undo announce")
|
|
||||||
case ap.ActivityBlock:
|
case ap.ActivityBlock:
|
||||||
if err := f.undoBlock(ctx, receivingAcct, requestingAcct, undo, objType); err != nil {
|
if err := f.undoBlock(
|
||||||
errs.Appendf("error undoing block: %w", err)
|
ctx,
|
||||||
|
receivingAcct,
|
||||||
|
requestingAcct,
|
||||||
|
undo,
|
||||||
|
asType,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UNDO ANNOUNCE
|
||||||
|
case ap.ActivityAnnounce:
|
||||||
|
// TODO: actually handle this!
|
||||||
|
log.Warn(ctx, "skipped undo announce")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs.Combine()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *federatingDB) undoFollow(
|
func (f *federatingDB) undoFollow(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
receivingAccount *gtsmodel.Account,
|
receivingAcct *gtsmodel.Account,
|
||||||
requestingAccount *gtsmodel.Account,
|
requestingAcct *gtsmodel.Account,
|
||||||
undo vocab.ActivityStreamsUndo,
|
undo vocab.ActivityStreamsUndo,
|
||||||
t vocab.Type,
|
t vocab.Type,
|
||||||
) error {
|
) error {
|
||||||
Follow, ok := t.(vocab.ActivityStreamsFollow)
|
asFollow, ok := t.(vocab.ActivityStreamsFollow)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("undoFollow: couldn't parse vocab.Type into vocab.ActivityStreamsFollow")
|
err := fmt.Errorf("%T not parseable as vocab.ActivityStreamsFollow", t)
|
||||||
|
return gtserror.SetMalformed(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the undo actor owns the target.
|
// Make sure the Undo
|
||||||
if !sameActor(undo.GetActivityStreamsActor(), Follow.GetActivityStreamsActor()) {
|
// actor owns the target.
|
||||||
|
if !sameActor(
|
||||||
|
undo.GetActivityStreamsActor(),
|
||||||
|
asFollow.GetActivityStreamsActor(),
|
||||||
|
) {
|
||||||
// Ignore this Activity.
|
// Ignore this Activity.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
follow, err := f.converter.ASFollowToFollow(ctx, Follow)
|
// Convert AS Follow to barebones *gtsmodel.Follow,
|
||||||
if err != nil {
|
// retrieving origin + target accts from the db.
|
||||||
return fmt.Errorf("undoFollow: error converting ActivityStreams Follow to follow: %w", err)
|
follow, err := f.converter.ASFollowToFollow(
|
||||||
|
gtscontext.SetBarebones(ctx),
|
||||||
|
asFollow,
|
||||||
|
)
|
||||||
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
|
err := gtserror.Newf("error converting AS Follow to follow: %w", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We were missing origin or
|
||||||
|
// target for this Follow, so
|
||||||
|
// we cannot Undo anything.
|
||||||
|
if follow == nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure addressee is follow target.
|
// Ensure addressee is follow target.
|
||||||
if follow.TargetAccountID != receivingAccount.ID {
|
if follow.TargetAccountID != receivingAcct.ID {
|
||||||
// Ignore this Activity.
|
const text = "receivingAcct was not Follow target"
|
||||||
return nil
|
return gtserror.NewErrorForbidden(errors.New(text), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure requester is follow origin.
|
// Ensure requester is follow origin.
|
||||||
if follow.AccountID != requestingAccount.ID {
|
if follow.AccountID != requestingAcct.ID {
|
||||||
// Ignore this Activity.
|
const text = "requestingAcct was not Follow origin"
|
||||||
return nil
|
return gtserror.NewErrorForbidden(errors.New(text), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete any existing follow with this URI.
|
// Delete any existing follow with this URI.
|
||||||
if err := f.state.DB.DeleteFollowByURI(ctx, follow.URI); err != nil && !errors.Is(err, db.ErrNoEntries) {
|
err = f.state.DB.DeleteFollowByURI(ctx, follow.URI)
|
||||||
return fmt.Errorf("undoFollow: db error removing follow: %w", err)
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
|
err := gtserror.Newf("db error deleting follow: %w", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete any existing follow request with this URI.
|
// Delete any existing follow request with this URI.
|
||||||
if err := f.state.DB.DeleteFollowRequestByURI(ctx, follow.URI); err != nil && !errors.Is(err, db.ErrNoEntries) {
|
err = f.state.DB.DeleteFollowRequestByURI(ctx, follow.URI)
|
||||||
return fmt.Errorf("undoFollow: db error removing follow request: %w", err)
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
|
err := gtserror.Newf("db error deleting follow request: %w", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(ctx, "Follow undone")
|
log.Debug(ctx, "Follow undone")
|
||||||
|
@ -134,58 +182,89 @@ func (f *federatingDB) undoFollow(
|
||||||
|
|
||||||
func (f *federatingDB) undoLike(
|
func (f *federatingDB) undoLike(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
receivingAccount *gtsmodel.Account,
|
receivingAcct *gtsmodel.Account,
|
||||||
requestingAccount *gtsmodel.Account,
|
requestingAcct *gtsmodel.Account,
|
||||||
undo vocab.ActivityStreamsUndo,
|
undo vocab.ActivityStreamsUndo,
|
||||||
t vocab.Type,
|
t vocab.Type,
|
||||||
) error {
|
) error {
|
||||||
Like, ok := t.(vocab.ActivityStreamsLike)
|
asLike, ok := t.(vocab.ActivityStreamsLike)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("undoLike: couldn't parse vocab.Type into vocab.ActivityStreamsLike")
|
err := fmt.Errorf("%T not parseable as vocab.ActivityStreamsLike", t)
|
||||||
|
return gtserror.SetMalformed(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the undo actor owns the target.
|
// Make sure the Undo
|
||||||
if !sameActor(undo.GetActivityStreamsActor(), Like.GetActivityStreamsActor()) {
|
// actor owns the target.
|
||||||
|
if !sameActor(
|
||||||
|
undo.GetActivityStreamsActor(),
|
||||||
|
asLike.GetActivityStreamsActor(),
|
||||||
|
) {
|
||||||
// Ignore this Activity.
|
// Ignore this Activity.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fave, err := f.converter.ASLikeToFave(ctx, Like)
|
// Convert AS Like to barebones *gtsmodel.StatusFave,
|
||||||
if err != nil {
|
// retrieving liking acct and target status from the DB.
|
||||||
return fmt.Errorf("undoLike: error converting ActivityStreams Like to fave: %w", err)
|
fave, err := f.converter.ASLikeToFave(
|
||||||
|
gtscontext.SetBarebones(ctx),
|
||||||
|
asLike,
|
||||||
|
)
|
||||||
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
|
err := gtserror.Newf("error converting AS Like to fave: %w", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We were missing status, account,
|
||||||
|
// or other for this Like, so we
|
||||||
|
// cannot Undo anything.
|
||||||
|
if fave == nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure addressee is fave target.
|
// Ensure addressee is fave target.
|
||||||
if fave.TargetAccountID != receivingAccount.ID {
|
if fave.TargetAccountID != receivingAcct.ID {
|
||||||
// Ignore this Activity.
|
const text = "receivingAcct was not Fave target"
|
||||||
return nil
|
return gtserror.NewErrorForbidden(errors.New(text), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure requester is fave origin.
|
// Ensure requester is fave origin.
|
||||||
if fave.AccountID != requestingAccount.ID {
|
if fave.AccountID != requestingAcct.ID {
|
||||||
// Ignore this Activity.
|
const text = "requestingAcct was not Fave origin"
|
||||||
return nil
|
return gtserror.NewErrorForbidden(errors.New(text), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch fave from the DB so we know the ID to delete it.
|
||||||
|
//
|
||||||
// Ignore URI on Likes, since we often get multiple Likes
|
// Ignore URI on Likes, since we often get multiple Likes
|
||||||
// with the same target and account ID, but differing URIs.
|
// with the same target and account ID, but differing URIs.
|
||||||
// Instead, we'll select using account and target status.
|
// Instead, we'll select using account and target status.
|
||||||
|
//
|
||||||
// Regardless of the URI, we can read an Undo Like to mean
|
// Regardless of the URI, we can read an Undo Like to mean
|
||||||
// "I don't want to fave this post anymore".
|
// "I don't want to fave this post anymore".
|
||||||
fave, err = f.state.DB.GetStatusFave(gtscontext.SetBarebones(ctx), fave.AccountID, fave.StatusID)
|
fave, err = f.state.DB.GetStatusFave(
|
||||||
if err != nil {
|
gtscontext.SetBarebones(ctx),
|
||||||
if errors.Is(err, db.ErrNoEntries) {
|
fave.AccountID,
|
||||||
// We didn't have a like/fave
|
fave.StatusID,
|
||||||
// for this combo anyway, ignore.
|
)
|
||||||
return nil
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
}
|
err := gtserror.Newf(
|
||||||
// Real error.
|
"db error getting fave from %s targeting %s: %w",
|
||||||
return fmt.Errorf("undoLike: db error getting fave from %s targeting %s: %w", fave.AccountID, fave.StatusID, err)
|
fave.AccountID, fave.StatusID, err,
|
||||||
|
)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the status fave.
|
if fave == nil {
|
||||||
|
// We didn't have this fave
|
||||||
|
// stored anyway, so we can't
|
||||||
|
// Undo it, just ignore.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the fave.
|
||||||
if err := f.state.DB.DeleteStatusFaveByID(ctx, fave.ID); err != nil {
|
if err := f.state.DB.DeleteStatusFaveByID(ctx, fave.ID); err != nil {
|
||||||
return fmt.Errorf("undoLike: db error deleting fave %s: %w", fave.ID, err)
|
err := gtserror.Newf("db error deleting fave %s: %w", fave.ID, err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(ctx, "Like undone")
|
log.Debug(ctx, "Like undone")
|
||||||
|
@ -194,42 +273,62 @@ func (f *federatingDB) undoLike(
|
||||||
|
|
||||||
func (f *federatingDB) undoBlock(
|
func (f *federatingDB) undoBlock(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
receivingAccount *gtsmodel.Account,
|
receivingAcct *gtsmodel.Account,
|
||||||
requestingAccount *gtsmodel.Account,
|
requestingAcct *gtsmodel.Account,
|
||||||
undo vocab.ActivityStreamsUndo,
|
undo vocab.ActivityStreamsUndo,
|
||||||
t vocab.Type,
|
t vocab.Type,
|
||||||
) error {
|
) error {
|
||||||
Block, ok := t.(vocab.ActivityStreamsBlock)
|
asBlock, ok := t.(vocab.ActivityStreamsBlock)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("undoBlock: couldn't parse vocab.Type into vocab.ActivityStreamsBlock")
|
err := fmt.Errorf("%T not parseable as vocab.ActivityStreamsBlock", t)
|
||||||
|
return gtserror.SetMalformed(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the undo actor owns the target.
|
// Make sure the Undo
|
||||||
if !sameActor(undo.GetActivityStreamsActor(), Block.GetActivityStreamsActor()) {
|
// actor owns the target.
|
||||||
|
if !sameActor(
|
||||||
|
undo.GetActivityStreamsActor(),
|
||||||
|
asBlock.GetActivityStreamsActor(),
|
||||||
|
) {
|
||||||
// Ignore this Activity.
|
// Ignore this Activity.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := f.converter.ASBlockToBlock(ctx, Block)
|
// Convert AS Block to barebones *gtsmodel.Block,
|
||||||
if err != nil {
|
// retrieving origin + target accts from the DB.
|
||||||
return fmt.Errorf("undoBlock: error converting ActivityStreams Block to block: %w", err)
|
block, err := f.converter.ASBlockToBlock(
|
||||||
|
gtscontext.SetBarebones(ctx),
|
||||||
|
asBlock,
|
||||||
|
)
|
||||||
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
|
err := gtserror.Newf("error converting AS Block to block: %w", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We were missing origin or
|
||||||
|
// target for this Block, so
|
||||||
|
// we cannot Undo anything.
|
||||||
|
if block == nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure addressee is block target.
|
// Ensure addressee is block target.
|
||||||
if block.TargetAccountID != receivingAccount.ID {
|
if block.TargetAccountID != receivingAcct.ID {
|
||||||
// Ignore this Activity.
|
const text = "receivingAcct was not Block target"
|
||||||
return nil
|
return gtserror.NewErrorForbidden(errors.New(text), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure requester is block origin.
|
// Ensure requester is block origin.
|
||||||
if block.AccountID != requestingAccount.ID {
|
if block.AccountID != requestingAcct.ID {
|
||||||
// Ignore this Activity.
|
const text = "requestingAcct was not Block origin"
|
||||||
return nil
|
return gtserror.NewErrorForbidden(errors.New(text), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete any existing BLOCK
|
// Delete any existing block.
|
||||||
if err := f.state.DB.DeleteBlockByURI(ctx, block.URI); err != nil && !errors.Is(err, db.ErrNoEntries) {
|
err = f.state.DB.DeleteBlockByURI(ctx, block.URI)
|
||||||
return fmt.Errorf("undoBlock: db error removing block: %w", err)
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
|
err := gtserror.Newf("db error deleting block: %w", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(ctx, "Block undone")
|
log.Debug(ctx, "Block undone")
|
||||||
|
|
|
@ -861,7 +861,7 @@ func (c *Converter) getASObjectStatus(ctx context.Context, id string, with ap.Wi
|
||||||
// Check for status in database with provided object URI.
|
// Check for status in database with provided object URI.
|
||||||
status, err := c.state.DB.GetStatusByURI(ctx, object[0].String())
|
status, err := c.state.DB.GetStatusByURI(ctx, object[0].String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gtserror.Newf("error getting object account from database: %w", err)
|
return nil, gtserror.Newf("error getting object status from database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return status, nil
|
return status, nil
|
||||||
|
|
Loading…
Reference in New Issue