[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:
parent
bfe8144fda
commit
61141ac232
14 changed files with 610 additions and 433 deletions
|
@ -404,7 +404,8 @@ func (f *Federator) callForPubKey(
|
||||||
pubKeyID *url.URL,
|
pubKeyID *url.URL,
|
||||||
) ([]byte, gtserror.WithCode) {
|
) ([]byte, gtserror.WithCode) {
|
||||||
// Use a transport to dereference the remote.
|
// Use a transport to dereference the remote.
|
||||||
trans, err := f.transportController.NewTransportForUsername(
|
trans, err := f.transport.NewTransportForUsername(
|
||||||
|
|
||||||
// We're on a hot path: don't retry if req fails.
|
// We're on a hot path: don't retry if req fails.
|
||||||
gtscontext.SetFastFail(ctx),
|
gtscontext.SetFastFail(ctx),
|
||||||
requestedUsername,
|
requestedUsername,
|
||||||
|
|
87
internal/federation/federatingdb/block.go
Normal file
87
internal/federation/federatingdb/block.go
Normal 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
|
||||||
|
}
|
|
@ -20,9 +20,7 @@
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
@ -49,115 +47,36 @@
|
||||||
func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
||||||
log.DebugKV(ctx, "create", serialize{asType})
|
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)
|
activityContext := getActivityContext(ctx)
|
||||||
if activityContext.internal {
|
if activityContext.internal {
|
||||||
return nil // Already processed.
|
return nil // Already processed.
|
||||||
}
|
}
|
||||||
|
|
||||||
requestingAcct := activityContext.requestingAcct
|
requesting := activityContext.requestingAcct
|
||||||
receivingAcct := activityContext.receivingAcct
|
receiving := activityContext.receivingAcct
|
||||||
|
|
||||||
if requestingAcct.IsMoving() {
|
if requesting.IsMoving() {
|
||||||
// A Moving account
|
// A Moving account
|
||||||
// can't do this.
|
// can't do this.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache entry for this create activity ID for later
|
// Cast to the expected types we handle in this func.
|
||||||
// checks in the Exist() function if we see it again.
|
creatable, ok := asType.(vocab.ActivityStreamsCreate)
|
||||||
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)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("activityBlock: could not convert type to block")
|
log.Debugf(ctx, "unhandled object type: %s", asType.GetTypeName())
|
||||||
}
|
return nil
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var errs gtserror.MultiError
|
var errs gtserror.MultiError
|
||||||
|
|
||||||
// Extract objects from create activity.
|
// Extract objects from create activity.
|
||||||
objects := ap.ExtractObjects(create)
|
objects := ap.ExtractObjects(creatable)
|
||||||
|
|
||||||
// Extract PollOptionables (votes!) from objects slice.
|
// Extract PollOptionables (votes!) from objects slice.
|
||||||
optionables, objects := ap.ExtractPollOptionables(objects)
|
optionables, objects := ap.ExtractPollOptionables(objects)
|
||||||
|
@ -166,8 +85,8 @@ func (f *federatingDB) activityCreate(
|
||||||
// Handle provided poll vote(s) creation, this can
|
// Handle provided poll vote(s) creation, this can
|
||||||
// be for single or multiple votes in the same poll.
|
// be for single or multiple votes in the same poll.
|
||||||
err := f.createPollOptionables(ctx,
|
err := f.createPollOptionables(ctx,
|
||||||
receivingAccount,
|
receiving,
|
||||||
requestingAccount,
|
requesting,
|
||||||
optionables,
|
optionables,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -182,12 +101,12 @@ func (f *federatingDB) activityCreate(
|
||||||
for _, statusable := range statusables {
|
for _, statusable := range statusables {
|
||||||
// Check if this is a forwarded object, i.e. did
|
// Check if this is a forwarded object, i.e. did
|
||||||
// the account making the request also create this?
|
// the account making the request also create this?
|
||||||
forwarded := !isSender(statusable, requestingAccount)
|
forwarded := !isSender(statusable, requesting)
|
||||||
|
|
||||||
// Handle create event for this statusable.
|
// Handle create event for this statusable.
|
||||||
if err := f.createStatusable(ctx,
|
if err := f.createStatusable(ctx,
|
||||||
receivingAccount,
|
receiving,
|
||||||
requestingAccount,
|
requesting,
|
||||||
statusable,
|
statusable,
|
||||||
forwarded,
|
forwarded,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
@ -340,8 +259,7 @@ func (f *federatingDB) createStatusable(
|
||||||
//
|
//
|
||||||
// It does this to try to ensure thread completion, but
|
// It does this to try to ensure thread completion, but
|
||||||
// we have our own thread fetching mechanism anyway.
|
// we have our own thread fetching mechanism anyway.
|
||||||
log.Debugf(ctx,
|
log.Debugf(ctx, "status %s is not relevant to receiver (%v); dropping it",
|
||||||
"status %s is not relevant to receiver (%v); dropping it",
|
|
||||||
ap.GetJSONLDId(statusable), err,
|
ap.GetJSONLDId(statusable), err,
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
|
@ -351,8 +269,7 @@ func (f *federatingDB) createStatusable(
|
||||||
// gauge how much spam is being sent to them.
|
// gauge how much spam is being sent to them.
|
||||||
//
|
//
|
||||||
// TODO: add Prometheus metrics for this.
|
// TODO: add Prometheus metrics for this.
|
||||||
log.Infof(ctx,
|
log.Infof(ctx, "status %s looked like spam (%v); dropping it",
|
||||||
"status %s looked like spam (%v); dropping it",
|
|
||||||
ap.GetJSONLDId(statusable), err,
|
ap.GetJSONLDId(statusable), err,
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
|
@ -398,210 +315,3 @@ func (f *federatingDB) createStatusable(
|
||||||
|
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"github.com/superseriousbusiness/activity/streams"
|
"github.com/superseriousbusiness/activity/streams"
|
||||||
|
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||||
|
@ -115,8 +116,10 @@ func (suite *CreateTestSuite) TestCreateFlag1() {
|
||||||
suite.FailNow(err.Error())
|
suite.FailNow(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flag := t.(vocab.ActivityStreamsFlag)
|
||||||
|
|
||||||
ctx := createTestContext(reportedAccount, reportingAccount)
|
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())
|
suite.FailNow(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
"codeberg.org/gruf/go-cache/v3/simple"
|
"codeberg.org/gruf/go-cache/v3/simple"
|
||||||
"github.com/superseriousbusiness/activity/pub"
|
"github.com/superseriousbusiness/activity/pub"
|
||||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
"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/interaction"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/filter/spam"
|
"github.com/superseriousbusiness/gotosocial/internal/filter/spam"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||||
|
@ -34,18 +35,20 @@
|
||||||
// DB wraps the pub.Database interface with
|
// DB wraps the pub.Database interface with
|
||||||
// a couple of custom functions for GoToSocial.
|
// a couple of custom functions for GoToSocial.
|
||||||
type DB interface {
|
type DB interface {
|
||||||
// Default functionality.
|
// Default
|
||||||
|
// functionality.
|
||||||
pub.Database
|
pub.Database
|
||||||
|
|
||||||
/*
|
// Federating protocol overridden callback functionality.
|
||||||
Overridden functionality for calling from federatingProtocol.
|
Like(context.Context, vocab.ActivityStreamsLike) error
|
||||||
*/
|
Block(context.Context, vocab.ActivityStreamsBlock) error
|
||||||
|
Follow(context.Context, vocab.ActivityStreamsFollow) error
|
||||||
Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) error
|
Undo(context.Context, vocab.ActivityStreamsUndo) error
|
||||||
Accept(ctx context.Context, accept vocab.ActivityStreamsAccept) error
|
Accept(context.Context, vocab.ActivityStreamsAccept) error
|
||||||
Reject(ctx context.Context, reject vocab.ActivityStreamsReject) error
|
Reject(context.Context, vocab.ActivityStreamsReject) error
|
||||||
Announce(ctx context.Context, announce vocab.ActivityStreamsAnnounce) error
|
Announce(context.Context, vocab.ActivityStreamsAnnounce) error
|
||||||
Move(ctx context.Context, move vocab.ActivityStreamsMove) error
|
Move(context.Context, vocab.ActivityStreamsMove) error
|
||||||
|
Flag(context.Context, vocab.ActivityStreamsFlag) error
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Extra/convenience functionality.
|
Extra/convenience functionality.
|
||||||
|
@ -87,3 +90,9 @@ func New(
|
||||||
fdb.activityIDs.Init(0, 2048)
|
fdb.activityIDs.Init(0, 2048)
|
||||||
return &fdb
|
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{}{})
|
||||||
|
}
|
||||||
|
|
91
internal/federation/federatingdb/flag.go
Normal file
91
internal/federation/federatingdb/flag.go
Normal 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
|
||||||
|
}
|
84
internal/federation/federatingdb/follow.go
Normal file
84
internal/federation/federatingdb/follow.go
Normal 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
|
||||||
|
}
|
147
internal/federation/federatingdb/like.go
Normal file
147
internal/federation/federatingdb/like.go
Normal 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
|
||||||
|
}
|
|
@ -38,6 +38,9 @@
|
||||||
func (f *federatingDB) Move(ctx context.Context, move vocab.ActivityStreamsMove) error {
|
func (f *federatingDB) Move(ctx context.Context, move vocab.ActivityStreamsMove) error {
|
||||||
log.DebugKV(ctx, "move", serialize{move})
|
log.DebugKV(ctx, "move", serialize{move})
|
||||||
|
|
||||||
|
// Mark activity as handled.
|
||||||
|
f.storeActivityID(move)
|
||||||
|
|
||||||
activityContext := getActivityContext(ctx)
|
activityContext := getActivityContext(ctx)
|
||||||
if activityContext.internal {
|
if activityContext.internal {
|
||||||
// Already processed.
|
// Already processed.
|
||||||
|
|
|
@ -43,20 +43,24 @@
|
||||||
func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
||||||
log.DebugKV(ctx, "update", serialize{asType})
|
log.DebugKV(ctx, "update", serialize{asType})
|
||||||
|
|
||||||
|
// Mark activity as handled.
|
||||||
|
f.storeActivityID(asType)
|
||||||
|
|
||||||
|
// Extract relevant values from passed ctx.
|
||||||
activityContext := getActivityContext(ctx)
|
activityContext := getActivityContext(ctx)
|
||||||
if activityContext.internal {
|
if activityContext.internal {
|
||||||
return nil // Already processed.
|
return nil // Already processed.
|
||||||
}
|
}
|
||||||
|
|
||||||
requestingAcct := activityContext.requestingAcct
|
requesting := activityContext.requestingAcct
|
||||||
receivingAcct := activityContext.receivingAcct
|
receiving := activityContext.receivingAcct
|
||||||
|
|
||||||
if accountable, ok := ap.ToAccountable(asType); ok {
|
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 {
|
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)
|
log.Debugf(ctx, "unhandled object type: %T", asType)
|
||||||
|
|
|
@ -456,39 +456,8 @@ func (f *Federator) FederatingCallbacks(ctx context.Context) (
|
||||||
other []any,
|
other []any,
|
||||||
err error,
|
err error,
|
||||||
) {
|
) {
|
||||||
wrapped = pub.FederatingWrappedCallbacks{
|
wrapped = f.wrapped
|
||||||
// OnFollow determines what action to take for this
|
other = f.callback
|
||||||
// particular callback if a Follow Activity is handled.
|
|
||||||
//
|
|
||||||
// For our implementation, we always want to do nothing
|
|
||||||
// because we have internal logic for handling follows.
|
|
||||||
OnFollow: pub.OnFollowDoNothing,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override some default behaviors to trigger our own side effects.
|
|
||||||
other = []any{
|
|
||||||
func(ctx context.Context, undo vocab.ActivityStreamsUndo) error {
|
|
||||||
return f.FederatingDB().Undo(ctx, undo)
|
|
||||||
},
|
|
||||||
func(ctx context.Context, accept vocab.ActivityStreamsAccept) error {
|
|
||||||
return f.FederatingDB().Accept(ctx, accept)
|
|
||||||
},
|
|
||||||
func(ctx context.Context, reject vocab.ActivityStreamsReject) error {
|
|
||||||
return f.FederatingDB().Reject(ctx, reject)
|
|
||||||
},
|
|
||||||
func(ctx context.Context, announce vocab.ActivityStreamsAnnounce) error {
|
|
||||||
return f.FederatingDB().Announce(ctx, announce)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define some of our own behaviors which are not
|
|
||||||
// overrides of the default pub.FederatingWrappedCallbacks.
|
|
||||||
other = append(other, []any{
|
|
||||||
func(ctx context.Context, move vocab.ActivityStreamsMove) error {
|
|
||||||
return f.FederatingDB().Move(ctx, move)
|
|
||||||
},
|
|
||||||
}...)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,14 +36,19 @@
|
||||||
} = (*Federator)(nil)
|
} = (*Federator)(nil)
|
||||||
|
|
||||||
type Federator struct {
|
type Federator struct {
|
||||||
db db.DB
|
db db.DB
|
||||||
federatingDB federatingdb.DB
|
federatingDB federatingdb.DB
|
||||||
clock pub.Clock
|
clock pub.Clock
|
||||||
converter *typeutils.Converter
|
converter *typeutils.Converter
|
||||||
transportController transport.Controller
|
transport transport.Controller
|
||||||
mediaManager *media.Manager
|
mediaManager *media.Manager
|
||||||
actor pub.FederatingActor
|
actor pub.FederatingActor
|
||||||
dereferencing.Dereferencer
|
dereferencing.Dereferencer
|
||||||
|
|
||||||
|
// store result of FederatingCallbacks() ahead
|
||||||
|
// of time since it's called in every PostInbox().
|
||||||
|
wrapped pub.FederatingWrappedCallbacks
|
||||||
|
callback []any
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFederator returns a new federator instance.
|
// NewFederator returns a new federator instance.
|
||||||
|
@ -58,12 +63,13 @@ func NewFederator(
|
||||||
) *Federator {
|
) *Federator {
|
||||||
clock := &Clock{}
|
clock := &Clock{}
|
||||||
f := &Federator{
|
f := &Federator{
|
||||||
db: state.DB,
|
db: state.DB,
|
||||||
federatingDB: federatingDB,
|
federatingDB: federatingDB,
|
||||||
clock: clock,
|
clock: clock,
|
||||||
converter: converter,
|
converter: converter,
|
||||||
transportController: transportController,
|
transport: transportController,
|
||||||
mediaManager: mediaManager,
|
mediaManager: mediaManager,
|
||||||
|
|
||||||
Dereferencer: dereferencing.NewDereferencer(
|
Dereferencer: dereferencing.NewDereferencer(
|
||||||
state,
|
state,
|
||||||
converter,
|
converter,
|
||||||
|
@ -72,6 +78,28 @@ func NewFederator(
|
||||||
intFilter,
|
intFilter,
|
||||||
mediaManager,
|
mediaManager,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// prepared response to FederatingCallbacks()
|
||||||
|
wrapped: pub.FederatingWrappedCallbacks{
|
||||||
|
|
||||||
|
// OnFollow determines what action to take for this
|
||||||
|
// particular callback if a Follow Activity is handled.
|
||||||
|
//
|
||||||
|
// For our implementation, we always want to do nothing
|
||||||
|
// because we have internal logic for handling follows.
|
||||||
|
OnFollow: pub.OnFollowDoNothing,
|
||||||
|
},
|
||||||
|
callback: []any{
|
||||||
|
federatingDB.Like,
|
||||||
|
federatingDB.Block,
|
||||||
|
federatingDB.Follow,
|
||||||
|
federatingDB.Undo,
|
||||||
|
federatingDB.Accept,
|
||||||
|
federatingDB.Reject,
|
||||||
|
federatingDB.Announce,
|
||||||
|
federatingDB.Move,
|
||||||
|
federatingDB.Flag,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
actor := newFederatingActor(f, f, federatingDB, clock)
|
actor := newFederatingActor(f, f, federatingDB, clock)
|
||||||
f.actor = actor
|
f.actor = actor
|
||||||
|
@ -90,5 +118,5 @@ func (f *Federator) FederatingDB() federatingdb.DB {
|
||||||
|
|
||||||
// TransportController returns the underlying transport controller.
|
// TransportController returns the underlying transport controller.
|
||||||
func (f *Federator) TransportController() transport.Controller {
|
func (f *Federator) TransportController() transport.Controller {
|
||||||
return f.transportController
|
return f.transport
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,5 +68,5 @@ func (f *Federator) NewTransport(ctx context.Context, actorBoxIRI *url.URL, _ st
|
||||||
return nil, fmt.Errorf("id %s was neither an inbox path nor an outbox path", actorBoxIRI.String())
|
return nil, fmt.Errorf("id %s was neither an inbox path nor an outbox path", actorBoxIRI.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.transportController.NewTransportForUsername(ctx, username)
|
return f.transport.NewTransportForUsername(ctx, username)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
package gtserror
|
package gtserror
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -53,37 +53,78 @@ type WithCode interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type withCode struct {
|
type withCode struct {
|
||||||
original error
|
err error
|
||||||
safe error
|
safe string
|
||||||
code int
|
code int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e withCode) Unwrap() error {
|
func (e *withCode) Unwrap() error {
|
||||||
return e.original
|
return e.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e withCode) Error() string {
|
func (e *withCode) Error() string {
|
||||||
return e.original.Error()
|
return e.err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e withCode) Safe() string {
|
func (e *withCode) Safe() string {
|
||||||
return e.safe.Error()
|
return e.safe
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e withCode) Code() int {
|
func (e *withCode) Code() int {
|
||||||
return e.code
|
return e.code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWithCode returns a new gtserror.WithCode that implements the error interface
|
||||||
|
// with given HTTP status code, providing status message of "${httpStatus}: ${msg}".
|
||||||
|
func NewWithCode(code int, msg string) WithCode {
|
||||||
|
return &withCode{
|
||||||
|
err: newAt(3, msg),
|
||||||
|
safe: http.StatusText(code) + ": " + msg,
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewfWithCode returns a new formatted gtserror.WithCode that implements the error interface
|
||||||
|
// with given HTTP status code, provided formatted status message of "${httpStatus}: ${msg}".
|
||||||
|
func NewfWithCode(code int, msgf string, args ...any) WithCode {
|
||||||
|
msg := fmt.Sprintf(msgf, args...)
|
||||||
|
return &withCode{
|
||||||
|
err: newAt(3, msg),
|
||||||
|
safe: http.StatusText(code) + ": " + msg,
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithCodeSafe returns a new gtserror.WithCode wrapping error with given HTTP status
|
||||||
|
// code, hiding error message externally, providing status message of "${httpStatus}: ${safe}".
|
||||||
|
func NewWithCodeSafe(code int, err error, safe string) WithCode {
|
||||||
|
return &withCode{
|
||||||
|
err: err,
|
||||||
|
safe: http.StatusText(code) + ": " + safe,
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapWithCode returns a new gtserror.WithCode wrapping error with given HTTP
|
||||||
|
// status code, hiding error message externally, providing standard status message.
|
||||||
|
func WrapWithCode(code int, err error) WithCode {
|
||||||
|
return &withCode{
|
||||||
|
err: err,
|
||||||
|
safe: http.StatusText(code),
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewErrorBadRequest returns an ErrorWithCode 400 with the given original error and optional help text.
|
// NewErrorBadRequest returns an ErrorWithCode 400 with the given original error and optional help text.
|
||||||
func NewErrorBadRequest(original error, helpText ...string) WithCode {
|
func NewErrorBadRequest(original error, helpText ...string) WithCode {
|
||||||
safe := http.StatusText(http.StatusBadRequest)
|
safe := http.StatusText(http.StatusBadRequest)
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
return withCode{
|
return &withCode{
|
||||||
original: original,
|
err: original,
|
||||||
safe: errors.New(safe),
|
safe: safe,
|
||||||
code: http.StatusBadRequest,
|
code: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,10 +134,10 @@ func NewErrorUnauthorized(original error, helpText ...string) WithCode {
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
return withCode{
|
return &withCode{
|
||||||
original: original,
|
err: original,
|
||||||
safe: errors.New(safe),
|
safe: safe,
|
||||||
code: http.StatusUnauthorized,
|
code: http.StatusUnauthorized,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,10 +147,10 @@ func NewErrorForbidden(original error, helpText ...string) WithCode {
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
return withCode{
|
return &withCode{
|
||||||
original: original,
|
err: original,
|
||||||
safe: errors.New(safe),
|
safe: safe,
|
||||||
code: http.StatusForbidden,
|
code: http.StatusForbidden,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,10 +160,10 @@ func NewErrorNotFound(original error, helpText ...string) WithCode {
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
return withCode{
|
return &withCode{
|
||||||
original: original,
|
err: original,
|
||||||
safe: errors.New(safe),
|
safe: safe,
|
||||||
code: http.StatusNotFound,
|
code: http.StatusNotFound,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,10 +173,10 @@ func NewErrorInternalError(original error, helpText ...string) WithCode {
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
return withCode{
|
return &withCode{
|
||||||
original: original,
|
err: original,
|
||||||
safe: errors.New(safe),
|
safe: safe,
|
||||||
code: http.StatusInternalServerError,
|
code: http.StatusInternalServerError,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,10 +186,10 @@ func NewErrorConflict(original error, helpText ...string) WithCode {
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
return withCode{
|
return &withCode{
|
||||||
original: original,
|
err: original,
|
||||||
safe: errors.New(safe),
|
safe: safe,
|
||||||
code: http.StatusConflict,
|
code: http.StatusConflict,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,10 +199,10 @@ func NewErrorNotAcceptable(original error, helpText ...string) WithCode {
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
return withCode{
|
return &withCode{
|
||||||
original: original,
|
err: original,
|
||||||
safe: errors.New(safe),
|
safe: safe,
|
||||||
code: http.StatusNotAcceptable,
|
code: http.StatusNotAcceptable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,10 +212,10 @@ func NewErrorUnprocessableEntity(original error, helpText ...string) WithCode {
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
return withCode{
|
return &withCode{
|
||||||
original: original,
|
err: original,
|
||||||
safe: errors.New(safe),
|
safe: safe,
|
||||||
code: http.StatusUnprocessableEntity,
|
code: http.StatusUnprocessableEntity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,10 +225,10 @@ func NewErrorGone(original error, helpText ...string) WithCode {
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
return withCode{
|
return &withCode{
|
||||||
original: original,
|
err: original,
|
||||||
safe: errors.New(safe),
|
safe: safe,
|
||||||
code: http.StatusGone,
|
code: http.StatusGone,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,10 +238,10 @@ func NewErrorNotImplemented(original error, helpText ...string) WithCode {
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
return withCode{
|
return &withCode{
|
||||||
original: original,
|
err: original,
|
||||||
safe: errors.New(safe),
|
safe: safe,
|
||||||
code: http.StatusNotImplemented,
|
code: http.StatusNotImplemented,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,10 +249,10 @@ func NewErrorNotImplemented(original error, helpText ...string) WithCode {
|
||||||
// This error type should only be used when an http caller has already hung up their request.
|
// This error type should only be used when an http caller has already hung up their request.
|
||||||
// See: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#nginx
|
// See: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#nginx
|
||||||
func NewErrorClientClosedRequest(original error) WithCode {
|
func NewErrorClientClosedRequest(original error) WithCode {
|
||||||
return withCode{
|
return &withCode{
|
||||||
original: original,
|
err: original,
|
||||||
safe: errors.New(StatusTextClientClosedRequest),
|
safe: StatusTextClientClosedRequest,
|
||||||
code: StatusClientClosedRequest,
|
code: StatusClientClosedRequest,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,9 +260,9 @@ func NewErrorClientClosedRequest(original error) WithCode {
|
||||||
// This error type should only be used when the server has decided to hang up a client
|
// This error type should only be used when the server has decided to hang up a client
|
||||||
// request after x amount of time, to avoid keeping extremely slow client requests open.
|
// request after x amount of time, to avoid keeping extremely slow client requests open.
|
||||||
func NewErrorRequestTimeout(original error) WithCode {
|
func NewErrorRequestTimeout(original error) WithCode {
|
||||||
return withCode{
|
return &withCode{
|
||||||
original: original,
|
err: original,
|
||||||
safe: errors.New(http.StatusText(http.StatusRequestTimeout)),
|
safe: http.StatusText(http.StatusRequestTimeout),
|
||||||
code: http.StatusRequestTimeout,
|
code: http.StatusRequestTimeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue