diff options
Diffstat (limited to 'internal/federation')
| -rw-r--r-- | internal/federation/dereferencing/status.go | 16 | ||||
| -rw-r--r-- | internal/federation/dereferencing/status_permitted.go | 128 | 
2 files changed, 130 insertions, 14 deletions
| diff --git a/internal/federation/dereferencing/status.go b/internal/federation/dereferencing/status.go index 2338f55f8..a3c1b7371 100644 --- a/internal/federation/dereferencing/status.go +++ b/internal/federation/dereferencing/status.go @@ -373,7 +373,7 @@ func (d *Dereferencer) enrichStatus(  	requestUser string,  	uri *url.URL,  	status *gtsmodel.Status, -	apubStatus ap.Statusable, +	statusable ap.Statusable,  ) (  	*gtsmodel.Status,  	ap.Statusable, @@ -393,7 +393,7 @@ func (d *Dereferencer) enrichStatus(  		return nil, nil, gtserror.SetUnretrievable(err)  	} -	if apubStatus == nil { +	if statusable == nil {  		// Dereference latest version of the status.  		rsp, err := tsport.Dereference(ctx, uri)  		if err != nil { @@ -402,7 +402,7 @@ func (d *Dereferencer) enrichStatus(  		}  		// 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.  		_ = rsp.Body.Close() @@ -444,7 +444,7 @@ func (d *Dereferencer) enrichStatus(  	}  	// Get the attributed-to account in order to fetch profile. -	attributedTo, err := ap.ExtractAttributedToURI(apubStatus) +	attributedTo, err := ap.ExtractAttributedToURI(statusable)  	if err != nil {  		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  	// 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 {  		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(  		uri,  		append( -			ap.GetURL(apubStatus),      // status URL(s) -			ap.GetJSONLDId(apubStatus), // status URI +			ap.GetURL(statusable),      // status URL(s) +			ap.GetJSONLDId(statusable), // status URI  		)...,  	)  	if err != nil { @@ -591,7 +591,7 @@ func (d *Dereferencer) enrichStatus(  		}  	} -	return latestStatus, apubStatus, nil +	return latestStatus, statusable, nil  }  func (d *Dereferencer) fetchStatusMentions( diff --git a/internal/federation/dereferencing/status_permitted.go b/internal/federation/dereferencing/status_permitted.go index 9bd74811e..2aecfc9b7 100644 --- a/internal/federation/dereferencing/status_permitted.go +++ b/internal/federation/dereferencing/status_permitted.go @@ -19,12 +19,18 @@ package dereferencing  import (  	"context" +	"errors"  	"net/url" +	"time"  	"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/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/id"  	"github.com/superseriousbusiness/gotosocial/internal/log" +	"github.com/superseriousbusiness/gotosocial/internal/uris"  	"github.com/superseriousbusiness/gotosocial/internal/util"  ) @@ -43,6 +49,14 @@ import (  // pending approval, then "PendingApproval" will be set  // to "true" on status. Callers should check this  // 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(  	ctx context.Context,  	requestUser string, @@ -58,7 +72,7 @@ func (d *Dereferencer) isPermittedStatus(  		log.Warnf(ctx, "status author suspended: %s", status.AccountURI)  		permitted = false -	case status.InReplyTo != nil: +	case status.InReplyToURI != "":  		// Status is a reply, check permissivity.  		permitted, err = d.isPermittedReply(ctx,  			requestUser, @@ -101,8 +115,90 @@ func (d *Dereferencer) isPermittedReply(  	requestUser string,  	status *gtsmodel.Status,  ) (bool, error) { -	// Extract reply from status. -	inReplyTo := status.InReplyTo +	var ( +		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 != "" {  		// We do not permit replies to @@ -142,8 +238,28 @@ func (d *Dereferencer) isPermittedReply(  	}  	if replyable.Forbidden() { -		// Replier is not permitted -		// to do this interaction. +		// Reply is not permitted. +		// +		// 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  	} @@ -193,7 +309,7 @@ func (d *Dereferencer) isPermittedReply(  		ctx,  		requestUser,  		status.ApprovedByURI, -		status.URI, +		statusURI,  		inReplyTo.AccountURI,  	); err != nil { | 
