[chore] remove type switch in Create() and instead move to FederatedCallbacks() (#3697)

* remove type switch in Create() and instead move to FederatedCallbacks()

* add missing (my bad!) federating wrapped callbacks behaviour

* add missing license header 😇

* fix create flag test to use correct function
This commit is contained in:
kim 2025-01-28 20:22:23 +00:00 committed by GitHub
commit 61141ac232
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 610 additions and 433 deletions

View file

@ -0,0 +1,87 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package federatingdb
import (
"context"
"net/http"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/messages"
)
func (f *federatingDB) Block(ctx context.Context, blockable vocab.ActivityStreamsBlock) error {
log.DebugKV(ctx, "block", serialize{blockable})
// Extract relevant values from passed ctx.
activityContext := getActivityContext(ctx)
if activityContext.internal {
return nil // Already processed.
}
requesting := activityContext.requestingAcct
receiving := activityContext.receivingAcct
if receiving.IsMoving() {
// A Moving account
// can't do this.
return nil
}
// Convert received AS block type to internal model.
block, err := f.converter.ASBlockToBlock(ctx, blockable)
if err != nil {
err := gtserror.Newf("error converting from AS type: %w", err)
return gtserror.WrapWithCode(http.StatusBadRequest, err)
}
// Ensure block enacted by correct account.
if block.AccountID != requesting.ID {
return gtserror.NewfWithCode(http.StatusForbidden, "requester %s is not expected actor %s",
requesting.URI, block.Account.URI)
}
// Ensure block received by correct account.
if block.TargetAccountID != receiving.ID {
return gtserror.NewfWithCode(http.StatusForbidden, "receiver %s is not expected object %s",
receiving.URI, block.TargetAccount.URI)
}
// Generate new ID for block.
block.ID = id.NewULID()
// Insert the new validated block into the database.
if err := f.state.DB.PutBlock(ctx, block); err != nil {
return gtserror.Newf("error inserting %s into db: %w", block.URI, err)
}
// Push message to worker queue to handle block side-effects.
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
APObjectType: ap.ActivityBlock,
APActivityType: ap.ActivityCreate,
GTSModel: block,
Receiving: receiving,
Requesting: requesting,
})
return nil
}

View file

@ -20,9 +20,7 @@ package federatingdb
import (
"context"
"errors"
"fmt"
"github.com/miekg/dns"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db"
@ -49,115 +47,36 @@ import (
func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
log.DebugKV(ctx, "create", serialize{asType})
// Cache entry for this activity type's ID for later
// checks in the Exist() function if we see it again.
f.activityIDs.Set(ap.GetJSONLDId(asType).String(), struct{}{})
// Extract relevant values from passed ctx.
activityContext := getActivityContext(ctx)
if activityContext.internal {
return nil // Already processed.
}
requestingAcct := activityContext.requestingAcct
receivingAcct := activityContext.receivingAcct
requesting := activityContext.requestingAcct
receiving := activityContext.receivingAcct
if requestingAcct.IsMoving() {
if requesting.IsMoving() {
// A Moving account
// can't do this.
return nil
}
// Cache entry for this create activity ID for later
// checks in the Exist() function if we see it again.
f.activityIDs.Set(ap.GetJSONLDId(asType).String(), struct{}{})
switch name := asType.GetTypeName(); name {
case ap.ActivityBlock:
// BLOCK SOMETHING
return f.activityBlock(ctx, asType, receivingAcct, requestingAcct)
case ap.ActivityCreate:
// CREATE SOMETHING
return f.activityCreate(ctx, asType, receivingAcct, requestingAcct)
case ap.ActivityFollow:
// FOLLOW SOMETHING
return f.activityFollow(ctx, asType, receivingAcct, requestingAcct)
case ap.ActivityLike:
// LIKE SOMETHING
return f.activityLike(ctx, asType, receivingAcct, requestingAcct)
case ap.ActivityFlag:
// FLAG / REPORT SOMETHING
return f.activityFlag(ctx, asType, receivingAcct, requestingAcct)
default:
log.Debugf(ctx, "unhandled object type: %s", name)
}
return nil
}
/*
BLOCK HANDLERS
*/
func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, receiving *gtsmodel.Account, requesting *gtsmodel.Account) error {
blockable, ok := asType.(vocab.ActivityStreamsBlock)
// Cast to the expected types we handle in this func.
creatable, ok := asType.(vocab.ActivityStreamsCreate)
if !ok {
return errors.New("activityBlock: could not convert type to block")
}
block, err := f.converter.ASBlockToBlock(ctx, blockable)
if err != nil {
return fmt.Errorf("activityBlock: could not convert Block to gts model block")
}
if block.AccountID != requesting.ID {
return fmt.Errorf(
"activityBlock: requestingAccount %s is not Block actor account %s",
requesting.URI, block.Account.URI,
)
}
if block.TargetAccountID != receiving.ID {
return fmt.Errorf(
"activityBlock: inbox account %s is not Block object account %s",
receiving.URI, block.TargetAccount.URI,
)
}
block.ID = id.NewULID()
if err := f.state.DB.PutBlock(ctx, block); err != nil {
return fmt.Errorf("activityBlock: database error inserting block: %s", err)
}
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
APObjectType: ap.ActivityBlock,
APActivityType: ap.ActivityCreate,
GTSModel: block,
Receiving: receiving,
Requesting: requesting,
})
return nil
}
/*
CREATE HANDLERS
*/
// activityCreate handles asType Create by checking
// the Object entries of the Create and calling other
// handlers as appropriate.
func (f *federatingDB) activityCreate(
ctx context.Context,
asType vocab.Type,
receivingAccount *gtsmodel.Account,
requestingAccount *gtsmodel.Account,
) error {
create, ok := asType.(vocab.ActivityStreamsCreate)
if !ok {
return gtserror.Newf("could not convert asType %T to ActivityStreamsCreate", asType)
log.Debugf(ctx, "unhandled object type: %s", asType.GetTypeName())
return nil
}
var errs gtserror.MultiError
// Extract objects from create activity.
objects := ap.ExtractObjects(create)
objects := ap.ExtractObjects(creatable)
// Extract PollOptionables (votes!) from objects slice.
optionables, objects := ap.ExtractPollOptionables(objects)
@ -166,8 +85,8 @@ func (f *federatingDB) activityCreate(
// Handle provided poll vote(s) creation, this can
// be for single or multiple votes in the same poll.
err := f.createPollOptionables(ctx,
receivingAccount,
requestingAccount,
receiving,
requesting,
optionables,
)
if err != nil {
@ -182,12 +101,12 @@ func (f *federatingDB) activityCreate(
for _, statusable := range statusables {
// Check if this is a forwarded object, i.e. did
// the account making the request also create this?
forwarded := !isSender(statusable, requestingAccount)
forwarded := !isSender(statusable, requesting)
// Handle create event for this statusable.
if err := f.createStatusable(ctx,
receivingAccount,
requestingAccount,
receiving,
requesting,
statusable,
forwarded,
); err != nil {
@ -340,8 +259,7 @@ func (f *federatingDB) createStatusable(
//
// It does this to try to ensure thread completion, but
// we have our own thread fetching mechanism anyway.
log.Debugf(ctx,
"status %s is not relevant to receiver (%v); dropping it",
log.Debugf(ctx, "status %s is not relevant to receiver (%v); dropping it",
ap.GetJSONLDId(statusable), err,
)
return nil
@ -351,8 +269,7 @@ func (f *federatingDB) createStatusable(
// gauge how much spam is being sent to them.
//
// TODO: add Prometheus metrics for this.
log.Infof(ctx,
"status %s looked like spam (%v); dropping it",
log.Infof(ctx, "status %s looked like spam (%v); dropping it",
ap.GetJSONLDId(statusable), err,
)
return nil
@ -398,210 +315,3 @@ func (f *federatingDB) createStatusable(
return nil
}
/*
FOLLOW HANDLERS
*/
func (f *federatingDB) activityFollow(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error {
follow, ok := asType.(vocab.ActivityStreamsFollow)
if !ok {
return errors.New("activityFollow: could not convert type to follow")
}
followRequest, err := f.converter.ASFollowToFollowRequest(ctx, follow)
if err != nil {
return fmt.Errorf("activityFollow: could not convert Follow to follow request: %s", err)
}
if followRequest.AccountID != requestingAccount.ID {
return fmt.Errorf(
"activityFollow: requestingAccount %s is not Follow actor account %s",
requestingAccount.URI, followRequest.Account.URI,
)
}
if followRequest.TargetAccountID != receivingAccount.ID {
return fmt.Errorf(
"activityFollow: inbox account %s is not Follow object account %s",
receivingAccount.URI, followRequest.TargetAccount.URI,
)
}
followRequest.ID = id.NewULID()
if err := f.state.DB.PutFollowRequest(ctx, followRequest); err != nil {
return fmt.Errorf("activityFollow: database error inserting follow request: %s", err)
}
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
APObjectType: ap.ActivityFollow,
APActivityType: ap.ActivityCreate,
GTSModel: followRequest,
Receiving: receivingAccount,
Requesting: requestingAccount,
})
return nil
}
/*
LIKE HANDLERS
*/
func (f *federatingDB) activityLike(
ctx context.Context,
asType vocab.Type,
receivingAcct *gtsmodel.Account,
requestingAcct *gtsmodel.Account,
) error {
like, ok := asType.(vocab.ActivityStreamsLike)
if !ok {
err := gtserror.Newf("could not convert asType %T to ActivityStreamsLike", asType)
return gtserror.SetMalformed(err)
}
fave, err := f.converter.ASLikeToFave(ctx, like)
if err != nil {
return gtserror.Newf("could not convert Like to fave: %w", err)
}
// Ensure requester not trying to
// Like on someone else's behalf.
if fave.AccountID != requestingAcct.ID {
text := fmt.Sprintf(
"requestingAcct %s is not Like actor account %s",
requestingAcct.URI, fave.Account.URI,
)
return gtserror.NewErrorForbidden(errors.New(text), text)
}
if !*fave.Status.Local {
// Only process likes of local statuses.
// TODO: process for remote statuses as well.
return nil
}
// Ensure valid Like target for requester.
policyResult, err := f.intFilter.StatusLikeable(ctx,
requestingAcct,
fave.Status,
)
if err != nil {
err := gtserror.Newf("error seeing if status %s is likeable: %w", fave.Status.ID, err)
return gtserror.NewErrorInternalError(err)
}
if policyResult.Forbidden() {
const errText = "requester does not have permission to Like this status"
err := gtserror.New(errText)
return gtserror.NewErrorForbidden(err, errText)
}
// Derive pendingApproval
// and preapproved status.
var (
pendingApproval bool
preApproved bool
)
switch {
case policyResult.WithApproval():
// Requester allowed to do
// this pending approval.
pendingApproval = true
case policyResult.MatchedOnCollection():
// Requester allowed to do this,
// but matched on collection.
// Preapprove Like and have the
// processor send out an Accept.
pendingApproval = true
preApproved = true
case policyResult.Permitted():
// Requester straight up
// permitted to do this,
// no need for Accept.
pendingApproval = false
}
// Set appropriate fields
// on fave and store it.
fave.ID = id.NewULID()
fave.PendingApproval = &pendingApproval
fave.PreApproved = preApproved
if err := f.state.DB.PutStatusFave(ctx, fave); err != nil {
if errors.Is(err, db.ErrAlreadyExists) {
// The fave already exists in the
// database, which means we've already
// handled side effects. We can just
// return nil here and be done with it.
return nil
}
return gtserror.Newf("db error inserting fave: %w", err)
}
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
APObjectType: ap.ActivityLike,
APActivityType: ap.ActivityCreate,
GTSModel: fave,
Receiving: receivingAcct,
Requesting: requestingAcct,
})
return nil
}
/*
FLAG HANDLERS
*/
func (f *federatingDB) activityFlag(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error {
flag, ok := asType.(vocab.ActivityStreamsFlag)
if !ok {
return errors.New("activityFlag: could not convert type to flag")
}
report, err := f.converter.ASFlagToReport(ctx, flag)
if err != nil {
return fmt.Errorf("activityFlag: could not convert Flag to report: %w", err)
}
// Requesting account must have at
// least two domains from the right
// in common with reporting account.
if dns.CompareDomainName(
requestingAccount.Domain,
report.Account.Domain,
) < 2 {
return fmt.Errorf(
"activityFlag: requesting account %s does not share a domain with Flag Actor account %s",
requestingAccount.URI, report.Account.URI,
)
}
if report.TargetAccountID != receivingAccount.ID {
return fmt.Errorf(
"activityFlag: inbox account %s is not Flag object account %s",
receivingAccount.URI, report.TargetAccount.URI,
)
}
report.ID = id.NewULID()
if err := f.state.DB.PutReport(ctx, report); err != nil {
return fmt.Errorf("activityFlag: database error inserting report: %w", err)
}
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
APObjectType: ap.ActivityFlag,
APActivityType: ap.ActivityCreate,
GTSModel: report,
Receiving: receivingAccount,
Requesting: requestingAccount,
})
return nil
}

View file

@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
@ -115,8 +116,10 @@ func (suite *CreateTestSuite) TestCreateFlag1() {
suite.FailNow(err.Error())
}
flag := t.(vocab.ActivityStreamsFlag)
ctx := createTestContext(reportedAccount, reportingAccount)
if err := suite.federatingDB.Create(ctx, t); err != nil {
if err := suite.federatingDB.Flag(ctx, flag); err != nil {
suite.FailNow(err.Error())
}

View file

@ -24,6 +24,7 @@ import (
"codeberg.org/gruf/go-cache/v3/simple"
"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/filter/interaction"
"github.com/superseriousbusiness/gotosocial/internal/filter/spam"
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
@ -34,18 +35,20 @@ import (
// DB wraps the pub.Database interface with
// a couple of custom functions for GoToSocial.
type DB interface {
// Default functionality.
// Default
// functionality.
pub.Database
/*
Overridden functionality for calling from federatingProtocol.
*/
Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) error
Accept(ctx context.Context, accept vocab.ActivityStreamsAccept) error
Reject(ctx context.Context, reject vocab.ActivityStreamsReject) error
Announce(ctx context.Context, announce vocab.ActivityStreamsAnnounce) error
Move(ctx context.Context, move vocab.ActivityStreamsMove) error
// Federating protocol overridden callback functionality.
Like(context.Context, vocab.ActivityStreamsLike) error
Block(context.Context, vocab.ActivityStreamsBlock) error
Follow(context.Context, vocab.ActivityStreamsFollow) error
Undo(context.Context, vocab.ActivityStreamsUndo) error
Accept(context.Context, vocab.ActivityStreamsAccept) error
Reject(context.Context, vocab.ActivityStreamsReject) error
Announce(context.Context, vocab.ActivityStreamsAnnounce) error
Move(context.Context, vocab.ActivityStreamsMove) error
Flag(context.Context, vocab.ActivityStreamsFlag) error
/*
Extra/convenience functionality.
@ -87,3 +90,9 @@ func New(
fdb.activityIDs.Init(0, 2048)
return &fdb
}
// storeActivityID stores an entry in the .activityIDs cache for this
// type's JSON-LD ID, for later checks in Exist() to mark it as seen.
func (f *federatingDB) storeActivityID(asType vocab.Type) {
f.activityIDs.Set(ap.GetJSONLDId(asType).String(), struct{}{})
}

View file

@ -0,0 +1,91 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package federatingdb
import (
"context"
"net/http"
"github.com/miekg/dns"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/messages"
)
func (f *federatingDB) Flag(ctx context.Context, flaggable vocab.ActivityStreamsFlag) error {
log.DebugKV(ctx, "flag", serialize{flaggable})
// Mark activity as handled.
f.storeActivityID(flaggable)
// Extract relevant values from passed ctx.
activityContext := getActivityContext(ctx)
if activityContext.internal {
return nil // Already processed.
}
requesting := activityContext.requestingAcct
receiving := activityContext.receivingAcct
// Convert received AS flag type to internal report model.
report, err := f.converter.ASFlagToReport(ctx, flaggable)
if err != nil {
err := gtserror.Newf("error converting from AS type: %w", err)
return gtserror.WrapWithCode(http.StatusBadRequest, err)
}
// Requesting acc's domain must be at
// least a subdomain of the reporting
// account. i.e. if they're using a
// different account domain to host.
if dns.CompareDomainName(
requesting.Domain,
report.Account.Domain,
) < 2 {
return gtserror.NewfWithCode(http.StatusForbidden, "requester %s does not share a domain with Flag Actor account %s",
requesting.URI, report.Account.URI)
}
// Ensure report received by correct account.
if report.TargetAccountID != receiving.ID {
return gtserror.NewfWithCode(http.StatusForbidden, "receiver %s is not expected object %s",
receiving.URI, report.TargetAccount.URI)
}
// Generate new ID for report.
report.ID = id.NewULID()
// Insert the new validated reported into the database.
if err := f.state.DB.PutReport(ctx, report); err != nil {
return gtserror.Newf("error inserting %s into db: %w", report.URI, err)
}
// Push message to worker queue to handle report side-effects.
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
APObjectType: ap.ActivityFlag,
APActivityType: ap.ActivityCreate,
GTSModel: report,
Receiving: receiving,
Requesting: requesting,
})
return nil
}

View file

@ -0,0 +1,84 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package federatingdb
import (
"context"
"net/http"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/messages"
)
func (f *federatingDB) Follow(ctx context.Context, followable vocab.ActivityStreamsFollow) error {
log.DebugKV(ctx, "follow", serialize{followable})
// Mark activity as handled.
f.storeActivityID(followable)
// Extract relevant values from passed ctx.
activityContext := getActivityContext(ctx)
if activityContext.internal {
return nil // Already processed.
}
requesting := activityContext.requestingAcct
receiving := activityContext.receivingAcct
// Convert received AS block type to internal follow request model.
followreq, err := f.converter.ASFollowToFollowRequest(ctx, followable)
if err != nil {
err := gtserror.Newf("error converting from AS type: %w", err)
return gtserror.WrapWithCode(http.StatusBadRequest, err)
}
// Ensure follow enacted by correct account.
if followreq.AccountID != requesting.ID {
return gtserror.NewfWithCode(http.StatusForbidden, "requester %s is not expected actor %s",
requesting.URI, followreq.Account.URI)
}
// Ensure follow received by correct account.
if followreq.TargetAccountID != receiving.ID {
return gtserror.NewfWithCode(http.StatusForbidden, "receiver %s is not expected object %s",
receiving.URI, followreq.TargetAccount.URI)
}
// Generate new ID for followreq.
followreq.ID = id.NewULID()
// Insert the new validate follow request into the database.
if err := f.state.DB.PutFollowRequest(ctx, followreq); err != nil {
return gtserror.Newf("error inserting %s into db: %w", followreq.URI, err)
}
// Push message to worker queue to handle followreq side-effects.
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
APObjectType: ap.ActivityFollow,
APActivityType: ap.ActivityCreate,
GTSModel: followreq,
Receiving: receiving,
Requesting: requesting,
})
return nil
}

View file

@ -0,0 +1,147 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package federatingdb
import (
"context"
"errors"
"net/http"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/messages"
)
func (f *federatingDB) Like(ctx context.Context, likeable vocab.ActivityStreamsLike) error {
log.DebugKV(ctx, "like", serialize{likeable})
// Mark activity as handled.
f.storeActivityID(likeable)
// Extract relevant values from passed ctx.
activityContext := getActivityContext(ctx)
if activityContext.internal {
return nil // Already processed.
}
requesting := activityContext.requestingAcct
receiving := activityContext.receivingAcct
if receiving.IsMoving() {
// A Moving account
// can't do this.
return nil
}
// Convert received AS like type to internal fave model.
fave, err := f.converter.ASLikeToFave(ctx, likeable)
if err != nil {
err := gtserror.Newf("error converting from AS type: %w", err)
return gtserror.WrapWithCode(http.StatusBadRequest, err)
}
// Ensure fave enacted by correct account.
if fave.AccountID != requesting.ID {
return gtserror.NewfWithCode(http.StatusForbidden, "requester %s is not expected actor %s",
requesting.URI, fave.Account.URI)
}
// Ensure fave received by correct account.
if fave.TargetAccountID != receiving.ID {
return gtserror.NewfWithCode(http.StatusForbidden, "receiver %s is not expected object %s",
receiving.URI, fave.TargetAccount.URI)
}
if !*fave.Status.Local {
// Only process likes of local statuses.
// TODO: process for remote statuses as well.
return nil
}
// Ensure valid Like target for requester.
policyResult, err := f.intFilter.StatusLikeable(ctx,
requesting,
fave.Status,
)
if err != nil {
return gtserror.Newf("error seeing if status %s is likeable: %w", fave.Status.URI, err)
}
if policyResult.Forbidden() {
return gtserror.NewWithCode(http.StatusForbidden, "requester does not have permission to Like status")
}
// Derive pendingApproval
// and preapproved status.
var (
pendingApproval bool
preApproved bool
)
switch {
case policyResult.WithApproval():
// Requester allowed to do
// this pending approval.
pendingApproval = true
case policyResult.MatchedOnCollection():
// Requester allowed to do this,
// but matched on collection.
// Preapprove Like and have the
// processor send out an Accept.
pendingApproval = true
preApproved = true
case policyResult.Permitted():
// Requester straight up
// permitted to do this,
// no need for Accept.
pendingApproval = false
}
// Set appropriate fields
// on fave and store it.
fave.ID = id.NewULID()
fave.PendingApproval = &pendingApproval
fave.PreApproved = preApproved
if err := f.state.DB.PutStatusFave(ctx, fave); err != nil {
if errors.Is(err, db.ErrAlreadyExists) {
// The fave already exists in the
// database, which means we've already
// handled side effects. We can just
// return nil here and be done with it.
return nil
}
return gtserror.Newf("error inserting %s into db: %w", fave.URI, err)
}
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
APObjectType: ap.ActivityLike,
APActivityType: ap.ActivityCreate,
GTSModel: fave,
Receiving: receiving,
Requesting: requesting,
})
return nil
}

View file

@ -38,6 +38,9 @@ import (
func (f *federatingDB) Move(ctx context.Context, move vocab.ActivityStreamsMove) error {
log.DebugKV(ctx, "move", serialize{move})
// Mark activity as handled.
f.storeActivityID(move)
activityContext := getActivityContext(ctx)
if activityContext.internal {
// Already processed.

View file

@ -43,20 +43,24 @@ import (
func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
log.DebugKV(ctx, "update", serialize{asType})
// Mark activity as handled.
f.storeActivityID(asType)
// Extract relevant values from passed ctx.
activityContext := getActivityContext(ctx)
if activityContext.internal {
return nil // Already processed.
}
requestingAcct := activityContext.requestingAcct
receivingAcct := activityContext.receivingAcct
requesting := activityContext.requestingAcct
receiving := activityContext.receivingAcct
if accountable, ok := ap.ToAccountable(asType); ok {
return f.updateAccountable(ctx, receivingAcct, requestingAcct, accountable)
return f.updateAccountable(ctx, receiving, requesting, accountable)
}
if statusable, ok := ap.ToStatusable(asType); ok {
return f.updateStatusable(ctx, receivingAcct, requestingAcct, statusable)
return f.updateStatusable(ctx, receiving, requesting, statusable)
}
log.Debugf(ctx, "unhandled object type: %T", asType)