diff options
Diffstat (limited to 'internal/federation/dereferencing/status.go')
-rw-r--r-- | internal/federation/dereferencing/status.go | 96 |
1 files changed, 93 insertions, 3 deletions
diff --git a/internal/federation/dereferencing/status.go b/internal/federation/dereferencing/status.go index 397d2aa28..c8012178c 100644 --- a/internal/federation/dereferencing/status.go +++ b/internal/federation/dereferencing/status.go @@ -503,9 +503,16 @@ func (d *Dereferencer) enrichStatus( latestStatus.FetchedAt = time.Now() latestStatus.Local = status.Local - // Ensure the status' poll remains consistent, else reset the poll. - if err := d.fetchStatusPoll(ctx, status, latestStatus); err != nil { - return nil, nil, gtserror.Newf("error populating poll for status %s: %w", uri, err) + // Check if this is a permitted status we should accept. + permit, err := d.isPermittedStatus(ctx, status, latestStatus) + if err != nil { + return nil, nil, gtserror.Newf("error checking permissibility for status %s: %w", uri, err) + } + + if !permit { + // Return a checkable error type that can be ignored. + err := gtserror.Newf("dropping unpermitted status: %s", uri) + return nil, nil, gtserror.SetNotPermitted(err) } // Ensure the status' mentions are populated, and pass in existing to check for changes. @@ -513,6 +520,11 @@ func (d *Dereferencer) enrichStatus( return nil, nil, gtserror.Newf("error populating mentions for status %s: %w", uri, err) } + // Ensure the status' poll remains consistent, else reset the poll. + if err := d.fetchStatusPoll(ctx, status, latestStatus); err != nil { + return nil, nil, gtserror.Newf("error populating poll for status %s: %w", uri, err) + } + // Now that we know who this status replies to (handled by ASStatusToStatus) // and who it mentions, we can add a ThreadID to it if necessary. if err := d.threadStatus(ctx, latestStatus); err != nil { @@ -550,6 +562,84 @@ func (d *Dereferencer) enrichStatus( return latestStatus, apubStatus, nil } +// isPermittedStatus returns whether the given status +// is permitted to be stored on this instance, checking +// whether the author is suspended, and passes visibility +// checks against status being replied-to (if any). +func (d *Dereferencer) isPermittedStatus( + ctx context.Context, + existing *gtsmodel.Status, + status *gtsmodel.Status, +) ( + permitted bool, // is permitted? + err error, +) { + + // our failure condition handling + // at the end of this function for + // the case of permission = false. + onFail := func() (bool, error) { + if existing != nil { + log.Infof(ctx, "deleting unpermitted: %s", existing.URI) + + // Delete existing status from database as it's no longer permitted. + if err := d.state.DB.DeleteStatusByID(ctx, existing.ID); err != nil { + log.Errorf(ctx, "error deleting %s after permissivity fail: %v", existing.URI, err) + } + } + return false, nil + } + + if !status.Account.SuspendedAt.IsZero() { + // The status author is suspended, + // this shouldn't have reached here + // but it's a fast check anyways. + return onFail() + } + + if status.InReplyToURI == "" { + // This status isn't in + // reply to anything! + return true, nil + } + + if status.InReplyTo == nil { + // If no inReplyTo has been set, + // we return here for now as we + // can't perform further checks. + // + // Worst case we allow something + // through, and later on during + // refetch it will get deleted. + return true, nil + } + + if status.InReplyTo.BoostOfID != "" { + // We do not permit replies to + // boost wrapper statuses. (this + // shouldn't be able to happen). + return onFail() + } + + // Check visibility of inReplyTo to status author. + permitted, err = d.visibility.StatusVisible(ctx, + status.Account, + status.InReplyTo, + ) + if err != nil { + return false, gtserror.Newf("error checking in-reply-to visibility: %w", err) + } + + if permitted && + *status.InReplyTo.Replyable { + // This status is visible AND + // replyable, in this economy?! + return true, nil + } + + return onFail() +} + // populateMentionTarget tries to populate the given // mention with the correct TargetAccount and (if not // yet set) TargetAccountURI, returning the populated |