diff --git a/cmd/gotosocial/action/server/server.go b/cmd/gotosocial/action/server/server.go index 8deed6ecc..376ade13d 100644 --- a/cmd/gotosocial/action/server/server.go +++ b/cmd/gotosocial/action/server/server.go @@ -213,7 +213,7 @@ var Start action.GTSAction = func(ctx context.Context) error { visFilter := visibility.NewFilter(state) intFilter := interaction.NewFilter(state) spamFilter := spam.NewFilter(state) - federatingDB := federatingdb.New(state, typeConverter, visFilter, spamFilter) + federatingDB := federatingdb.New(state, typeConverter, visFilter, intFilter, spamFilter) transportController := transport.NewController(state, federatingDB, &federation.Clock{}, client) federator := federation.NewFederator( state, diff --git a/internal/federation/federatingdb/create.go b/internal/federation/federatingdb/create.go index 2a780ed7c..60232efe3 100644 --- a/internal/federation/federatingdb/create.go +++ b/internal/federation/federatingdb/create.go @@ -445,42 +445,106 @@ func (f *federatingDB) activityFollow(ctx context.Context, asType vocab.Type, re LIKE HANDLERS */ -func (f *federatingDB) activityLike(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error { +func (f *federatingDB) activityLike( + ctx context.Context, + asType vocab.Type, + receivingAcct *gtsmodel.Account, + requestingAcct *gtsmodel.Account, +) error { like, ok := asType.(vocab.ActivityStreamsLike) if !ok { - return errors.New("activityLike: could not convert type to like") + 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 fmt.Errorf("activityLike: could not convert Like to fave: %w", err) + return gtserror.Newf("could not convert Like to fave: %w", err) } - if fave.AccountID != requestingAccount.ID { - return fmt.Errorf( - "activityLike: requestingAccount %s is not Like actor account %s", - requestingAccount.URI, fave.Account.URI, + // 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 Like already exists in the database, which - // means we've already handled side effects. We can - // just return nil here and be done with it. + // 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 fmt.Errorf("activityLike: database error inserting fave: %w", err) + 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: receivingAccount, - Requesting: requestingAccount, + Receiving: receivingAcct, + Requesting: requestingAcct, }) return nil diff --git a/internal/federation/federatingdb/db.go b/internal/federation/federatingdb/db.go index 3388d7a03..ba10ed98b 100644 --- a/internal/federation/federatingdb/db.go +++ b/internal/federation/federatingdb/db.go @@ -23,6 +23,7 @@ import ( "github.com/superseriousbusiness/activity/pub" "github.com/superseriousbusiness/activity/streams/vocab" + "github.com/superseriousbusiness/gotosocial/internal/filter/interaction" "github.com/superseriousbusiness/gotosocial/internal/filter/spam" "github.com/superseriousbusiness/gotosocial/internal/filter/visibility" "github.com/superseriousbusiness/gotosocial/internal/state" @@ -58,6 +59,7 @@ type federatingDB struct { state *state.State converter *typeutils.Converter visFilter *visibility.Filter + intFilter *interaction.Filter spamFilter *spam.Filter } @@ -67,12 +69,14 @@ func New( state *state.State, converter *typeutils.Converter, visFilter *visibility.Filter, + intFilter *interaction.Filter, spamFilter *spam.Filter, ) DB { fdb := federatingDB{ state: state, converter: converter, visFilter: visFilter, + intFilter: intFilter, spamFilter: spamFilter, } return &fdb diff --git a/testrig/federatingdb.go b/testrig/federatingdb.go index 41c5a8ae9..73731abae 100644 --- a/testrig/federatingdb.go +++ b/testrig/federatingdb.go @@ -19,6 +19,7 @@ package testrig import ( "github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb" + "github.com/superseriousbusiness/gotosocial/internal/filter/interaction" "github.com/superseriousbusiness/gotosocial/internal/filter/spam" "github.com/superseriousbusiness/gotosocial/internal/filter/visibility" "github.com/superseriousbusiness/gotosocial/internal/state" @@ -31,6 +32,7 @@ func NewTestFederatingDB(state *state.State) federatingdb.DB { state, typeutils.NewConverter(state), visibility.NewFilter(state), + interaction.NewFilter(state), spam.NewFilter(state), ) }