mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[feature] Federate interaction policies + Accepts; enforce policies (#3138)
* [feature] Federate interaction policies + Accepts; enforce policies * use Acceptable type * fix index * remove appendIRIStrs * add GetAccept federatingdb function * lock on object IRI
This commit is contained in:
@ -122,11 +122,23 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
|
||||
|
||||
// ACCEPT SOMETHING
|
||||
case ap.ActivityAccept:
|
||||
switch fMsg.APObjectType { //nolint:gocritic
|
||||
switch fMsg.APObjectType {
|
||||
|
||||
// ACCEPT FOLLOW
|
||||
// ACCEPT (pending) FOLLOW
|
||||
case ap.ActivityFollow:
|
||||
return p.fediAPI.AcceptFollow(ctx, fMsg)
|
||||
|
||||
// ACCEPT (pending) LIKE
|
||||
case ap.ActivityLike:
|
||||
return p.fediAPI.AcceptLike(ctx, fMsg)
|
||||
|
||||
// ACCEPT (pending) REPLY
|
||||
case ap.ObjectNote:
|
||||
return p.fediAPI.AcceptReply(ctx, fMsg)
|
||||
|
||||
// ACCEPT (pending) ANNOUNCE
|
||||
case ap.ActivityAnnounce:
|
||||
return p.fediAPI.AcceptAnnounce(ctx, fMsg)
|
||||
}
|
||||
|
||||
// DELETE SOMETHING
|
||||
@ -216,6 +228,52 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If pending approval is true then
|
||||
// status must reply to a LOCAL status
|
||||
// that requires approval for the reply.
|
||||
pendingApproval := util.PtrOrValue(
|
||||
status.PendingApproval,
|
||||
false,
|
||||
)
|
||||
|
||||
switch {
|
||||
case pendingApproval && !status.PreApproved:
|
||||
// If approval is required and status isn't
|
||||
// preapproved, then just notify the account
|
||||
// that's being interacted with: they can
|
||||
// approve or deny the interaction later.
|
||||
|
||||
// Notify *local* account of pending reply.
|
||||
if err := p.surface.notifyPendingReply(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error notifying pending reply: %v", err)
|
||||
}
|
||||
|
||||
// Return early.
|
||||
return nil
|
||||
|
||||
case pendingApproval && status.PreApproved:
|
||||
// If approval is required and status is
|
||||
// preapproved, that means this is a reply
|
||||
// to one of our statuses with permission
|
||||
// that matched on a following/followers
|
||||
// collection. Do the Accept immediately and
|
||||
// then process everything else as normal.
|
||||
|
||||
// Put approval in the database and
|
||||
// update the status with approvedBy URI.
|
||||
approval, err := p.utils.approveReply(ctx, status)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error pre-approving reply: %w", err)
|
||||
}
|
||||
|
||||
// Send out the approval as Accept.
|
||||
if err := p.federate.AcceptInteraction(ctx, approval); err != nil {
|
||||
return gtserror.Newf("error federating pre-approval of reply: %w", err)
|
||||
}
|
||||
|
||||
// Don't return, just continue as normal.
|
||||
}
|
||||
|
||||
// Update stats for the remote account.
|
||||
if err := p.utils.incrementStatusesCount(ctx, fMsg.Requesting, status); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
@ -348,6 +406,52 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er
|
||||
return gtserror.Newf("error populating status fave: %w", err)
|
||||
}
|
||||
|
||||
// If pending approval is true then
|
||||
// fave must target a LOCAL status
|
||||
// that requires approval for the fave.
|
||||
pendingApproval := util.PtrOrValue(
|
||||
fave.PendingApproval,
|
||||
false,
|
||||
)
|
||||
|
||||
switch {
|
||||
case pendingApproval && !fave.PreApproved:
|
||||
// If approval is required and fave isn't
|
||||
// preapproved, then just notify the account
|
||||
// that's being interacted with: they can
|
||||
// approve or deny the interaction later.
|
||||
|
||||
// Notify *local* account of pending fave.
|
||||
if err := p.surface.notifyPendingFave(ctx, fave); err != nil {
|
||||
log.Errorf(ctx, "error notifying pending fave: %v", err)
|
||||
}
|
||||
|
||||
// Return early.
|
||||
return nil
|
||||
|
||||
case pendingApproval && fave.PreApproved:
|
||||
// If approval is required and fave is
|
||||
// preapproved, that means this is a fave
|
||||
// of one of our statuses with permission
|
||||
// that matched on a following/followers
|
||||
// collection. Do the Accept immediately and
|
||||
// then process everything else as normal.
|
||||
|
||||
// Put approval in the database and
|
||||
// update the fave with approvedBy URI.
|
||||
approval, err := p.utils.approveFave(ctx, fave)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error pre-approving fave: %w", err)
|
||||
}
|
||||
|
||||
// Send out the approval as Accept.
|
||||
if err := p.federate.AcceptInteraction(ctx, approval); err != nil {
|
||||
return gtserror.Newf("error federating pre-approval of fave: %w", err)
|
||||
}
|
||||
|
||||
// Don't return, just continue as normal.
|
||||
}
|
||||
|
||||
if err := p.surface.notifyFave(ctx, fave); err != nil {
|
||||
log.Errorf(ctx, "error notifying fave: %v", err)
|
||||
}
|
||||
@ -365,8 +469,9 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Status", fMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Dereference status that this boosts, note
|
||||
// that this will handle storing the boost in
|
||||
// Dereference into a boost wrapper status.
|
||||
//
|
||||
// Note: this will handle storing the boost in
|
||||
// the db, and dereferencing the target status
|
||||
// ancestors / descendants where appropriate.
|
||||
var err error
|
||||
@ -376,8 +481,10 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
|
||||
fMsg.Receiving.Username,
|
||||
)
|
||||
if err != nil {
|
||||
if gtserror.IsUnretrievable(err) {
|
||||
// Boosted status domain blocked, nothing to do.
|
||||
if gtserror.IsUnretrievable(err) ||
|
||||
gtserror.NotPermitted(err) {
|
||||
// Boosted status domain blocked, or
|
||||
// otherwise not permitted, nothing to do.
|
||||
log.Debugf(ctx, "skipping announce: %v", err)
|
||||
return nil
|
||||
}
|
||||
@ -386,6 +493,52 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
|
||||
return gtserror.Newf("error dereferencing announce: %w", err)
|
||||
}
|
||||
|
||||
// If pending approval is true then
|
||||
// boost must target a LOCAL status
|
||||
// that requires approval for the boost.
|
||||
pendingApproval := util.PtrOrValue(
|
||||
boost.PendingApproval,
|
||||
false,
|
||||
)
|
||||
|
||||
switch {
|
||||
case pendingApproval && !boost.PreApproved:
|
||||
// If approval is required and boost isn't
|
||||
// preapproved, then just notify the account
|
||||
// that's being interacted with: they can
|
||||
// approve or deny the interaction later.
|
||||
|
||||
// Notify *local* account of pending announce.
|
||||
if err := p.surface.notifyPendingAnnounce(ctx, boost); err != nil {
|
||||
log.Errorf(ctx, "error notifying pending boost: %v", err)
|
||||
}
|
||||
|
||||
// Return early.
|
||||
return nil
|
||||
|
||||
case pendingApproval && boost.PreApproved:
|
||||
// If approval is required and status is
|
||||
// preapproved, that means this is a boost
|
||||
// of one of our statuses with permission
|
||||
// that matched on a following/followers
|
||||
// collection. Do the Accept immediately and
|
||||
// then process everything else as normal.
|
||||
|
||||
// Put approval in the database and
|
||||
// update the boost with approvedBy URI.
|
||||
approval, err := p.utils.approveAnnounce(ctx, boost)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error pre-approving boost: %w", err)
|
||||
}
|
||||
|
||||
// Send out the approval as Accept.
|
||||
if err := p.federate.AcceptInteraction(ctx, approval); err != nil {
|
||||
return gtserror.Newf("error federating pre-approval of boost: %w", err)
|
||||
}
|
||||
|
||||
// Don't return, just continue as normal.
|
||||
}
|
||||
|
||||
// Update stats for the remote account.
|
||||
if err := p.utils.incrementStatusesCount(ctx, fMsg.Requesting, boost); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
@ -549,6 +702,68 @@ func (p *fediAPI) AcceptFollow(ctx context.Context, fMsg *messages.FromFediAPI)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) AcceptLike(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
// TODO: Add something here if we ever implement sending out Likes to
|
||||
// followers more broadly and not just the owner of the Liked status.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) AcceptReply(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
status, ok := fMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Status", fMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Update stats for the actor account.
|
||||
if err := p.utils.incrementStatusesCount(ctx, status.Account, status); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
// Timeline and notify the status.
|
||||
if err := p.surface.timelineAndNotifyStatus(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error timelining and notifying status: %v", err)
|
||||
}
|
||||
|
||||
// Interaction counts changed on the replied-to status;
|
||||
// uncache the prepared version from all timelines.
|
||||
p.surface.invalidateStatusFromTimelines(ctx, status.InReplyToID)
|
||||
|
||||
// Send out the reply again, fully this time.
|
||||
if err := p.federate.CreateStatus(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error federating announce: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) AcceptAnnounce(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
boost, ok := fMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Status", fMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Update stats for the actor account.
|
||||
if err := p.utils.incrementStatusesCount(ctx, boost.Account, boost); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
// Timeline and notify the boost wrapper status.
|
||||
if err := p.surface.timelineAndNotifyStatus(ctx, boost); err != nil {
|
||||
log.Errorf(ctx, "error timelining and notifying status: %v", err)
|
||||
}
|
||||
|
||||
// Interaction counts changed on the boosted status;
|
||||
// uncache the prepared version from all timelines.
|
||||
p.surface.invalidateStatusFromTimelines(ctx, boost.BoostOfID)
|
||||
|
||||
// Send out the boost again, fully this time.
|
||||
if err := p.federate.Announce(ctx, boost); err != nil {
|
||||
log.Errorf(ctx, "error federating announce: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) UpdateStatus(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
// Cast the existing Status model attached to msg.
|
||||
existing, ok := fMsg.GTSModel.(*gtsmodel.Status)
|
||||
|
Reference in New Issue
Block a user