mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-10-31 22:40:01 +00:00
[chore] Reject replies to rejected replies (#3291)
* [chore] Reject replies to rejected replies * tweak * don't set URI for implicit Rejects
This commit is contained in:
parent
efd1a4f717
commit
71261c62c2
2 changed files with 130 additions and 14 deletions
|
@ -373,7 +373,7 @@ func (d *Dereferencer) enrichStatus(
|
||||||
requestUser string,
|
requestUser string,
|
||||||
uri *url.URL,
|
uri *url.URL,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
apubStatus ap.Statusable,
|
statusable ap.Statusable,
|
||||||
) (
|
) (
|
||||||
*gtsmodel.Status,
|
*gtsmodel.Status,
|
||||||
ap.Statusable,
|
ap.Statusable,
|
||||||
|
@ -393,7 +393,7 @@ func (d *Dereferencer) enrichStatus(
|
||||||
return nil, nil, gtserror.SetUnretrievable(err)
|
return nil, nil, gtserror.SetUnretrievable(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if apubStatus == nil {
|
if statusable == nil {
|
||||||
// Dereference latest version of the status.
|
// Dereference latest version of the status.
|
||||||
rsp, err := tsport.Dereference(ctx, uri)
|
rsp, err := tsport.Dereference(ctx, uri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -402,7 +402,7 @@ func (d *Dereferencer) enrichStatus(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to resolve ActivityPub status from response.
|
// Attempt to resolve ActivityPub status from response.
|
||||||
apubStatus, err = ap.ResolveStatusable(ctx, rsp.Body)
|
statusable, err = ap.ResolveStatusable(ctx, rsp.Body)
|
||||||
|
|
||||||
// Tidy up now done.
|
// Tidy up now done.
|
||||||
_ = rsp.Body.Close()
|
_ = rsp.Body.Close()
|
||||||
|
@ -444,7 +444,7 @@ func (d *Dereferencer) enrichStatus(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the attributed-to account in order to fetch profile.
|
// Get the attributed-to account in order to fetch profile.
|
||||||
attributedTo, err := ap.ExtractAttributedToURI(apubStatus)
|
attributedTo, err := ap.ExtractAttributedToURI(statusable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, gtserror.New("attributedTo was empty")
|
return nil, nil, gtserror.New("attributedTo was empty")
|
||||||
}
|
}
|
||||||
|
@ -460,7 +460,7 @@ func (d *Dereferencer) enrichStatus(
|
||||||
|
|
||||||
// ActivityPub model was recently dereferenced, so assume passed status
|
// ActivityPub model was recently dereferenced, so assume passed status
|
||||||
// may contain out-of-date information. Convert AP model to our GTS model.
|
// may contain out-of-date information. Convert AP model to our GTS model.
|
||||||
latestStatus, err := d.converter.ASStatusToStatus(ctx, apubStatus)
|
latestStatus, err := d.converter.ASStatusToStatus(ctx, statusable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, gtserror.Newf("error converting statusable to gts model for status %s: %w", uri, err)
|
return nil, nil, gtserror.Newf("error converting statusable to gts model for status %s: %w", uri, err)
|
||||||
}
|
}
|
||||||
|
@ -479,8 +479,8 @@ func (d *Dereferencer) enrichStatus(
|
||||||
matches, err := util.URIMatches(
|
matches, err := util.URIMatches(
|
||||||
uri,
|
uri,
|
||||||
append(
|
append(
|
||||||
ap.GetURL(apubStatus), // status URL(s)
|
ap.GetURL(statusable), // status URL(s)
|
||||||
ap.GetJSONLDId(apubStatus), // status URI
|
ap.GetJSONLDId(statusable), // status URI
|
||||||
)...,
|
)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -591,7 +591,7 @@ func (d *Dereferencer) enrichStatus(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return latestStatus, apubStatus, nil
|
return latestStatus, statusable, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dereferencer) fetchStatusMentions(
|
func (d *Dereferencer) fetchStatusMentions(
|
||||||
|
|
|
@ -19,12 +19,18 @@
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/uris"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,6 +49,14 @@
|
||||||
// pending approval, then "PendingApproval" will be set
|
// pending approval, then "PendingApproval" will be set
|
||||||
// to "true" on status. Callers should check this
|
// to "true" on status. Callers should check this
|
||||||
// and handle it as appropriate.
|
// and handle it as appropriate.
|
||||||
|
//
|
||||||
|
// If status is a reply that is not permitted based on
|
||||||
|
// interaction policies, or status replies to a status
|
||||||
|
// that's been Rejected before (ie., it has a rejected
|
||||||
|
// InteractionRequest stored in the db) then the reply
|
||||||
|
// will also be rejected, and a pre-rejected interaction
|
||||||
|
// request will be stored for it before doing cleanup,
|
||||||
|
// if one didn't already exist.
|
||||||
func (d *Dereferencer) isPermittedStatus(
|
func (d *Dereferencer) isPermittedStatus(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
requestUser string,
|
requestUser string,
|
||||||
|
@ -58,7 +72,7 @@ func (d *Dereferencer) isPermittedStatus(
|
||||||
log.Warnf(ctx, "status author suspended: %s", status.AccountURI)
|
log.Warnf(ctx, "status author suspended: %s", status.AccountURI)
|
||||||
permitted = false
|
permitted = false
|
||||||
|
|
||||||
case status.InReplyTo != nil:
|
case status.InReplyToURI != "":
|
||||||
// Status is a reply, check permissivity.
|
// Status is a reply, check permissivity.
|
||||||
permitted, err = d.isPermittedReply(ctx,
|
permitted, err = d.isPermittedReply(ctx,
|
||||||
requestUser,
|
requestUser,
|
||||||
|
@ -101,8 +115,90 @@ func (d *Dereferencer) isPermittedReply(
|
||||||
requestUser string,
|
requestUser string,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
// Extract reply from status.
|
var (
|
||||||
inReplyTo := status.InReplyTo
|
statusURI = status.URI // Definitely set.
|
||||||
|
inReplyToURI = status.InReplyToURI // Definitely set.
|
||||||
|
inReplyTo = status.InReplyTo // Might not yet be set.
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if status with this URI has previously been rejected.
|
||||||
|
req, err := d.state.DB.GetInteractionRequestByInteractionURI(
|
||||||
|
gtscontext.SetBarebones(ctx),
|
||||||
|
statusURI,
|
||||||
|
)
|
||||||
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
|
err := gtserror.Newf("db error getting interaction request: %w", err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if req != nil && req.IsRejected() {
|
||||||
|
// This status has been
|
||||||
|
// rejected reviously, so
|
||||||
|
// it's not permitted now.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if replied-to status has previously been rejected.
|
||||||
|
req, err = d.state.DB.GetInteractionRequestByInteractionURI(
|
||||||
|
gtscontext.SetBarebones(ctx),
|
||||||
|
inReplyToURI,
|
||||||
|
)
|
||||||
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
|
err := gtserror.Newf("db error getting interaction request: %w", err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if req != nil && req.IsRejected() {
|
||||||
|
// This status's parent was rejected, so
|
||||||
|
// implicitly this reply should be rejected too.
|
||||||
|
//
|
||||||
|
// We know already that we haven't inserted
|
||||||
|
// a rejected interaction request for this
|
||||||
|
// status yet so do it before returning.
|
||||||
|
id := id.NewULID()
|
||||||
|
|
||||||
|
// To ensure the Reject chain stays coherent,
|
||||||
|
// borrow fields from the up-thread rejection.
|
||||||
|
// This collapses the chain beyond the first
|
||||||
|
// rejected reply and allows us to avoid derefing
|
||||||
|
// further replies we already know we don't want.
|
||||||
|
statusID := req.StatusID
|
||||||
|
targetAccountID := req.TargetAccountID
|
||||||
|
|
||||||
|
// As nobody is actually Rejecting the reply
|
||||||
|
// directly, but it's an implicit Reject coming
|
||||||
|
// from our internal logic, don't bother setting
|
||||||
|
// a URI (it's not a required field anyway).
|
||||||
|
uri := ""
|
||||||
|
|
||||||
|
rejection := >smodel.InteractionRequest{
|
||||||
|
ID: id,
|
||||||
|
StatusID: statusID,
|
||||||
|
TargetAccountID: targetAccountID,
|
||||||
|
InteractingAccountID: status.AccountID,
|
||||||
|
InteractionURI: statusURI,
|
||||||
|
InteractionType: gtsmodel.InteractionReply,
|
||||||
|
URI: uri,
|
||||||
|
RejectedAt: time.Now(),
|
||||||
|
}
|
||||||
|
err := d.state.DB.PutInteractionRequest(ctx, rejection)
|
||||||
|
if err != nil && !errors.Is(err, db.ErrAlreadyExists) {
|
||||||
|
return false, gtserror.Newf("db error putting pre-rejected interaction request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if inReplyTo == nil {
|
||||||
|
// We didn't have the replied-to status in
|
||||||
|
// our database (yet) so we can't know if
|
||||||
|
// this reply is permitted or not. For now
|
||||||
|
// just return true; worst-case, the status
|
||||||
|
// sticks around on the instance for a couple
|
||||||
|
// hours until we try to dereference it again
|
||||||
|
// and realize it should be forbidden.
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
if inReplyTo.BoostOfID != "" {
|
if inReplyTo.BoostOfID != "" {
|
||||||
// We do not permit replies to
|
// We do not permit replies to
|
||||||
|
@ -142,8 +238,28 @@ func (d *Dereferencer) isPermittedReply(
|
||||||
}
|
}
|
||||||
|
|
||||||
if replyable.Forbidden() {
|
if replyable.Forbidden() {
|
||||||
// Replier is not permitted
|
// Reply is not permitted.
|
||||||
// to do this interaction.
|
//
|
||||||
|
// Insert a pre-rejected interaction request
|
||||||
|
// into the db and return. This ensures that
|
||||||
|
// replies to this now-rejected status aren't
|
||||||
|
// inadvertently permitted.
|
||||||
|
id := id.NewULID()
|
||||||
|
rejection := >smodel.InteractionRequest{
|
||||||
|
ID: id,
|
||||||
|
StatusID: inReplyTo.ID,
|
||||||
|
TargetAccountID: inReplyTo.AccountID,
|
||||||
|
InteractingAccountID: status.AccountID,
|
||||||
|
InteractionURI: statusURI,
|
||||||
|
InteractionType: gtsmodel.InteractionReply,
|
||||||
|
URI: uris.GenerateURIForReject(inReplyTo.Account.Username, id),
|
||||||
|
RejectedAt: time.Now(),
|
||||||
|
}
|
||||||
|
err := d.state.DB.PutInteractionRequest(ctx, rejection)
|
||||||
|
if err != nil && !errors.Is(err, db.ErrAlreadyExists) {
|
||||||
|
return false, gtserror.Newf("db error putting pre-rejected interaction request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +309,7 @@ func (d *Dereferencer) isPermittedReply(
|
||||||
ctx,
|
ctx,
|
||||||
requestUser,
|
requestUser,
|
||||||
status.ApprovedByURI,
|
status.ApprovedByURI,
|
||||||
status.URI,
|
statusURI,
|
||||||
inReplyTo.AccountURI,
|
inReplyTo.AccountURI,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue