commit 469da93678b3f738f65372d13dcd1ea7de390063
parent d6abe105b3aeb0dd35442f913df5082db9983aae
Author: tobi <31960611+tsmethurst@users.noreply.github.com>
Date: Mon, 23 May 2022 11:46:50 +0200
[security] Check all involved IRIs during block checking (#593)
* tidy up context keys, add otherInvolvedIRIs
* add ReplyToable interface
* skip block check if we own the requesting domain
* add block check for other involved IRIs
* use cacheable status fetch
* remove unused ContextActivity
* remove unused ContextActivity
* add helper for unique URIs
* check through CCs and clean slice
* add GetAccountIDForStatusURI
* add GetAccountIDForAccountURI
* check blocks on involved account
* add statuses to tests
* add some blocked tests
* go fmt
* extract Tos as well as CCs
* test PostInboxRequestBodyHook
* add some more testActivities
* deduplicate involvedAccountIDs
* go fmt
* use cacheable db functions, remove new functions
Diffstat:
9 files changed, 380 insertions(+), 51 deletions(-)
diff --git a/internal/ap/contextkey.go b/internal/ap/contextkey.go
@@ -22,20 +22,16 @@ package ap
type ContextKey string
const (
- // ContextActivity can be used to set and retrieve the actual go-fed pub.Activity within a context.
- ContextActivity ContextKey = "activity"
// ContextReceivingAccount can be used the set and retrieve the account being interacted with / receiving an activity in their inbox.
- ContextReceivingAccount ContextKey = "account"
+ ContextReceivingAccount ContextKey = "receivingAccount"
// ContextRequestingAccount can be used to set and retrieve the account of an incoming federation request.
// This will often be the actor of the instance that's posting the request.
ContextRequestingAccount ContextKey = "requestingAccount"
- // ContextRequestingActorIRI can be used to set and retrieve the actor of an incoming federation request.
- // This will usually be the owner of whatever activity is being posted.
- ContextRequestingActorIRI ContextKey = "requestingActorIRI"
+ // ContextOtherInvolvedIRIs can be used to set and retrieve a slice of all IRIs that are 'involved' in an Activity without being
+ // the receivingAccount or the requestingAccount. In other words, people or notes who are CC'ed or Replied To by an Activity.
+ ContextOtherInvolvedIRIs ContextKey = "otherInvolvedIRIs"
// ContextRequestingPublicKeyVerifier can be used to set and retrieve the public key verifier of an incoming federation request.
ContextRequestingPublicKeyVerifier ContextKey = "requestingPublicKeyVerifier"
// ContextRequestingPublicKeySignature can be used to set and retrieve the value of the signature header of an incoming federation request.
ContextRequestingPublicKeySignature ContextKey = "requestingPublicKeySignature"
- // ContextFromFederatorChan can be used to pass a pointer to the fromFederator channel into the federator for use in callbacks.
- ContextFromFederatorChan ContextKey = "fromFederatorChan"
)
diff --git a/internal/ap/interfaces.go b/internal/ap/interfaces.go
@@ -140,6 +140,11 @@ type Addressable interface {
WithCC
}
+// ReplyToable represents the minimum interface for an Activity that can be InReplyTo another activity.
+type ReplyToable interface {
+ WithInReplyTo
+}
+
// CollectionPageable represents the minimum interface for an activitystreams 'CollectionPage' object.
type CollectionPageable interface {
WithJSONLDId
diff --git a/internal/db/bundb/domain.go b/internal/db/bundb/domain.go
@@ -23,6 +23,8 @@ import (
"net/url"
"strings"
+ "github.com/spf13/viper"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
@@ -33,7 +35,7 @@ type domainDB struct {
}
func (d *domainDB) IsDomainBlocked(ctx context.Context, domain string) (bool, db.Error) {
- if domain == "" {
+ if domain == "" || domain == viper.GetString(config.Keys.Host) {
return false, nil
}
diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go
@@ -33,6 +33,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/uris"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
)
/*
@@ -62,19 +63,60 @@ import (
// write a response to the ResponseWriter as is expected that the caller
// to PostInbox will do so when handling the error.
func (f *federator) PostInboxRequestBodyHook(ctx context.Context, r *http.Request, activity pub.Activity) (context.Context, error) {
- l := logrus.WithFields(logrus.Fields{
- "func": "PostInboxRequestBodyHook",
- "useragent": r.UserAgent(),
- "url": r.URL.String(),
- })
+ // extract any other IRIs involved in this activity
+ otherInvolvedIRIs := []*url.URL{}
+
+ // check if the Activity itself has an 'inReplyTo'
+ if replyToable, ok := activity.(ap.ReplyToable); ok {
+ if inReplyToURI := ap.ExtractInReplyToURI(replyToable); inReplyToURI != nil {
+ otherInvolvedIRIs = append(otherInvolvedIRIs, inReplyToURI)
+ }
+ }
- if activity == nil {
- err := errors.New("nil activity in PostInboxRequestBodyHook")
- l.Debug(err)
- return nil, err
+ // now check if the Object of the Activity (usually a Note or something) has an 'inReplyTo'
+ if object := activity.GetActivityStreamsObject(); object != nil {
+ if replyToable, ok := object.(ap.ReplyToable); ok {
+ if inReplyToURI := ap.ExtractInReplyToURI(replyToable); inReplyToURI != nil {
+ otherInvolvedIRIs = append(otherInvolvedIRIs, inReplyToURI)
+ }
+ }
+ }
+
+ // check for Tos and CCs on Activity itself
+ if addressable, ok := activity.(ap.Addressable); ok {
+ if ccURIs, err := ap.ExtractCCs(addressable); err == nil {
+ otherInvolvedIRIs = append(otherInvolvedIRIs, ccURIs...)
+ }
+ if toURIs, err := ap.ExtractTos(addressable); err == nil {
+ otherInvolvedIRIs = append(otherInvolvedIRIs, toURIs...)
+ }
}
- // set the activity on the context for use later on
- return context.WithValue(ctx, ap.ContextActivity, activity), nil
+
+ // and on the Object itself
+ if object := activity.GetActivityStreamsObject(); object != nil {
+ if addressable, ok := object.(ap.Addressable); ok {
+ if ccURIs, err := ap.ExtractCCs(addressable); err == nil {
+ otherInvolvedIRIs = append(otherInvolvedIRIs, ccURIs...)
+ }
+ if toURIs, err := ap.ExtractTos(addressable); err == nil {
+ otherInvolvedIRIs = append(otherInvolvedIRIs, toURIs...)
+ }
+ }
+ }
+
+ // remove any duplicate entries in the slice we put together
+ deduped := util.UniqueURIs(otherInvolvedIRIs)
+
+ // clean any instances of the public URI since we don't care about that in this context
+ cleaned := []*url.URL{}
+ for _, u := range deduped {
+ if !pub.IsPublic(u.String()) {
+ cleaned = append(cleaned, u)
+ }
+ }
+
+ withOtherInvolvedIRIs := context.WithValue(ctx, ap.ContextOtherInvolvedIRIs, cleaned)
+ return withOtherInvolvedIRIs, nil
}
// AuthenticatePostInbox delegates the authentication of a POST to an
@@ -185,40 +227,85 @@ func (f *federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, er
})
l.Debugf("entering BLOCKED function with IRI list: %+v", actorIRIs)
+ // check domain blocks first for the given actor IRIs
+ blocked, err := f.db.AreURIsBlocked(ctx, actorIRIs)
+ if err != nil {
+ return false, fmt.Errorf("error checking domain blocks of actorIRIs: %s", err)
+ }
+ if blocked {
+ return blocked, nil
+ }
+
+ // check domain blocks for any other involved IRIs
+ otherInvolvedIRIsI := ctx.Value(ap.ContextOtherInvolvedIRIs)
+ otherInvolvedIRIs, ok := otherInvolvedIRIsI.([]*url.URL)
+ if !ok {
+ l.Errorf("other involved IRIs not set on request context")
+ return false, errors.New("other involved IRIs not set on request context, so couldn't determine blocks")
+ }
+ blocked, err = f.db.AreURIsBlocked(ctx, otherInvolvedIRIs)
+ if err != nil {
+ return false, fmt.Errorf("error checking domain blocks of otherInvolvedIRIs: %s", err)
+ }
+ if blocked {
+ return blocked, nil
+ }
+
+ // now check for user-level block from receiving against requesting account
receivingAccountI := ctx.Value(ap.ContextReceivingAccount)
receivingAccount, ok := receivingAccountI.(*gtsmodel.Account)
if !ok {
l.Errorf("receiving account not set on request context")
return false, errors.New("receiving account not set on request context, so couldn't determine blocks")
}
-
- blocked, err := f.db.AreURIsBlocked(ctx, actorIRIs)
+ requestingAccountI := ctx.Value(ap.ContextRequestingAccount)
+ requestingAccount, ok := requestingAccountI.(*gtsmodel.Account)
+ if !ok {
+ l.Errorf("requesting account not set on request context")
+ return false, errors.New("requesting account not set on request context, so couldn't determine blocks")
+ }
+ // the receiver shouldn't block the sender
+ blocked, err = f.db.IsBlocked(ctx, receivingAccount.ID, requestingAccount.ID, false)
if err != nil {
- return false, fmt.Errorf("error checking domain blocks: %s", err)
+ return false, fmt.Errorf("error checking user-level blocks: %s", err)
}
if blocked {
return blocked, nil
}
- for _, uri := range actorIRIs {
- requestingAccount, err := f.db.GetAccountByURI(ctx, uri.String())
+ // get account IDs for other involved accounts
+ var involvedAccountIDs []string
+ for _, iri := range otherInvolvedIRIs {
+ var involvedAccountID string
+ if involvedStatus, err := f.db.GetStatusByURI(ctx, iri.String()); err == nil {
+ involvedAccountID = involvedStatus.AccountID
+ } else if involvedAccount, err := f.db.GetAccountByURI(ctx, iri.String()); err == nil {
+ involvedAccountID = involvedAccount.ID
+ }
+
+ if involvedAccountID != "" {
+ involvedAccountIDs = append(involvedAccountIDs, involvedAccountID)
+ }
+ }
+ deduped := util.UniqueStrings(involvedAccountIDs)
+
+ for _, involvedAccountID := range deduped {
+ // the involved account shouldn't block whoever is making this request
+ blocked, err = f.db.IsBlocked(ctx, involvedAccountID, requestingAccount.ID, false)
if err != nil {
- if err == db.ErrNoEntries {
- // we don't have an entry for this account so it's not blocked
- // TODO: allow a different default to be set for this behavior
- l.Tracef("no entry for account with URI %s so it can't be blocked", uri)
- continue
- }
- return false, fmt.Errorf("error getting account with uri %s: %s", uri.String(), err)
+ return false, fmt.Errorf("error checking user-level otherInvolvedIRI blocks: %s", err)
+ }
+ if blocked {
+ return blocked, nil
}
- blocked, err = f.db.IsBlocked(ctx, receivingAccount.ID, requestingAccount.ID, false)
+ // whoever is receiving this request shouldn't block the involved account
+ blocked, err = f.db.IsBlocked(ctx, receivingAccount.ID, involvedAccountID, false)
if err != nil {
- return false, fmt.Errorf("error checking account block: %s", err)
+ return false, fmt.Errorf("error checking user-level otherInvolvedIRI blocks: %s", err)
}
if blocked {
- l.Tracef("local account %s blocks account with uri %s", receivingAccount.Username, uri)
- return true, nil
+ return blocked, nil
}
}
diff --git a/internal/federation/federatingprotocol_test.go b/internal/federation/federatingprotocol_test.go
@@ -22,11 +22,11 @@ import (
"context"
"net/http"
"net/http/httptest"
+ "net/url"
"testing"
"github.com/go-fed/httpsig"
"github.com/stretchr/testify/suite"
- "github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/concurrency"
"github.com/superseriousbusiness/gotosocial/internal/federation"
@@ -39,8 +39,7 @@ type FederatingProtocolTestSuite struct {
FederatorStandardTestSuite
}
-// make sure PostInboxRequestBodyHook properly sets the inbox username and activity on the context
-func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHook() {
+func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHook1() {
// the activity we're gonna use
activity := suite.testActivities["dm_for_zork"]
@@ -63,13 +62,82 @@ func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHook() {
suite.NoError(err)
suite.NotNil(newContext)
- // activity should be set on context now
- activityI := newContext.Value(ap.ContextActivity)
- suite.NotNil(activityI)
- returnedActivity, ok := activityI.(pub.Activity)
- suite.True(ok)
- suite.NotNil(returnedActivity)
- suite.EqualValues(activity.Activity, returnedActivity)
+ involvedIRIsI := newContext.Value(ap.ContextOtherInvolvedIRIs)
+ involvedIRIs, ok := involvedIRIsI.([]*url.URL)
+ if !ok {
+ suite.FailNow("couldn't get involved IRIs from context")
+ }
+
+ suite.Len(involvedIRIs, 1)
+ suite.Contains(involvedIRIs, testrig.URLMustParse("http://localhost:8080/users/the_mighty_zork"))
+}
+
+func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHook2() {
+ // the activity we're gonna use
+ activity := suite.testActivities["reply_to_turtle_for_zork"]
+
+ fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1)
+
+ // setup transport controller with a no-op client so we don't make external calls
+ tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(func(req *http.Request) (*http.Response, error) {
+ return nil, nil
+ }), suite.db, fedWorker)
+ // setup module being tested
+ federator := federation.NewFederator(suite.db, testrig.NewTestFederatingDB(suite.db, fedWorker), tc, suite.tc, testrig.NewTestMediaManager(suite.db, suite.storage))
+
+ // setup request
+ ctx := context.Background()
+ request := httptest.NewRequest(http.MethodPost, "http://localhost:8080/users/the_mighty_zork/inbox", nil) // the endpoint we're hitting
+ request.Header.Set("Signature", activity.SignatureHeader)
+
+ // trigger the function being tested, and return the new context it creates
+ newContext, err := federator.PostInboxRequestBodyHook(ctx, request, activity.Activity)
+ suite.NoError(err)
+ suite.NotNil(newContext)
+
+ involvedIRIsI := newContext.Value(ap.ContextOtherInvolvedIRIs)
+ involvedIRIs, ok := involvedIRIsI.([]*url.URL)
+ if !ok {
+ suite.FailNow("couldn't get involved IRIs from context")
+ }
+
+ suite.Len(involvedIRIs, 2)
+ suite.Contains(involvedIRIs, testrig.URLMustParse("http://localhost:8080/users/1happyturtle"))
+ suite.Contains(involvedIRIs, testrig.URLMustParse("http://fossbros-anonymous.io/users/foss_satan/followers"))
+}
+
+func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHook3() {
+ // the activity we're gonna use
+ activity := suite.testActivities["reply_to_turtle_for_turtle"]
+
+ fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1)
+
+ // setup transport controller with a no-op client so we don't make external calls
+ tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(func(req *http.Request) (*http.Response, error) {
+ return nil, nil
+ }), suite.db, fedWorker)
+ // setup module being tested
+ federator := federation.NewFederator(suite.db, testrig.NewTestFederatingDB(suite.db, fedWorker), tc, suite.tc, testrig.NewTestMediaManager(suite.db, suite.storage))
+
+ // setup request
+ ctx := context.Background()
+ request := httptest.NewRequest(http.MethodPost, "http://localhost:8080/users/1happyturtle/inbox", nil) // the endpoint we're hitting
+ request.Header.Set("Signature", activity.SignatureHeader)
+
+ // trigger the function being tested, and return the new context it creates
+ newContext, err := federator.PostInboxRequestBodyHook(ctx, request, activity.Activity)
+ suite.NoError(err)
+ suite.NotNil(newContext)
+
+ involvedIRIsI := newContext.Value(ap.ContextOtherInvolvedIRIs)
+ involvedIRIs, ok := involvedIRIsI.([]*url.URL)
+ if !ok {
+ suite.FailNow("couldn't get involved IRIs from context")
+ }
+
+ suite.Len(involvedIRIs, 2)
+ suite.Contains(involvedIRIs, testrig.URLMustParse("http://localhost:8080/users/1happyturtle"))
+ suite.Contains(involvedIRIs, testrig.URLMustParse("http://fossbros-anonymous.io/users/foss_satan/followers"))
}
func (suite *FederatingProtocolTestSuite) TestAuthenticatePostInbox() {
@@ -97,8 +165,7 @@ func (suite *FederatingProtocolTestSuite) TestAuthenticatePostInbox() {
// by the time AuthenticatePostInbox is called, PostInboxRequestBodyHook should have already been called,
// which should have set the account and username onto the request. We can replicate that behavior here:
ctxWithAccount := context.WithValue(ctx, ap.ContextReceivingAccount, inboxAccount)
- ctxWithActivity := context.WithValue(ctxWithAccount, ap.ContextActivity, activity)
- ctxWithVerifier := context.WithValue(ctxWithActivity, ap.ContextRequestingPublicKeyVerifier, verifier)
+ ctxWithVerifier := context.WithValue(ctxWithAccount, ap.ContextRequestingPublicKeyVerifier, verifier)
ctxWithSignature := context.WithValue(ctxWithVerifier, ap.ContextRequestingPublicKeySignature, activity.SignatureHeader)
// we can pass this recorder as a writer and read it back after
@@ -117,6 +184,125 @@ func (suite *FederatingProtocolTestSuite) TestAuthenticatePostInbox() {
suite.Equal(sendingAccount.Username, requestingAccount.Username)
}
+func (suite *FederatingProtocolTestSuite) TestBlocked1() {
+ fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1)
+ tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db, fedWorker)
+ federator := federation.NewFederator(suite.db, testrig.NewTestFederatingDB(suite.db, fedWorker), tc, suite.tc, testrig.NewTestMediaManager(suite.db, suite.storage))
+
+ sendingAccount := suite.testAccounts["remote_account_1"]
+ inboxAccount := suite.testAccounts["local_account_1"]
+ otherInvolvedIRIs := []*url.URL{}
+ actorIRIs := []*url.URL{
+ testrig.URLMustParse(sendingAccount.URI),
+ }
+
+ ctx := context.Background()
+ ctxWithReceivingAccount := context.WithValue(ctx, ap.ContextReceivingAccount, inboxAccount)
+ ctxWithRequestingAccount := context.WithValue(ctxWithReceivingAccount, ap.ContextRequestingAccount, sendingAccount)
+ ctxWithOtherInvolvedIRIs := context.WithValue(ctxWithRequestingAccount, ap.ContextOtherInvolvedIRIs, otherInvolvedIRIs)
+
+ blocked, err := federator.Blocked(ctxWithOtherInvolvedIRIs, actorIRIs)
+ suite.NoError(err)
+ suite.False(blocked)
+}
+
+func (suite *FederatingProtocolTestSuite) TestBlocked2() {
+ fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1)
+ tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db, fedWorker)
+ federator := federation.NewFederator(suite.db, testrig.NewTestFederatingDB(suite.db, fedWorker), tc, suite.tc, testrig.NewTestMediaManager(suite.db, suite.storage))
+
+ sendingAccount := suite.testAccounts["remote_account_1"]
+ inboxAccount := suite.testAccounts["local_account_1"]
+ otherInvolvedIRIs := []*url.URL{}
+ actorIRIs := []*url.URL{
+ testrig.URLMustParse(sendingAccount.URI),
+ }
+
+ ctx := context.Background()
+ ctxWithReceivingAccount := context.WithValue(ctx, ap.ContextReceivingAccount, inboxAccount)
+ ctxWithRequestingAccount := context.WithValue(ctxWithReceivingAccount, ap.ContextRequestingAccount, sendingAccount)
+ ctxWithOtherInvolvedIRIs := context.WithValue(ctxWithRequestingAccount, ap.ContextOtherInvolvedIRIs, otherInvolvedIRIs)
+
+ // insert a block from inboxAccount targeting sendingAccount
+ if err := suite.db.Put(context.Background(), >smodel.Block{
+ ID: "01G3KBEMJD4VQ2D615MPV7KTRD",
+ URI: "whatever",
+ AccountID: inboxAccount.ID,
+ TargetAccountID: sendingAccount.ID,
+ }); err != nil {
+ suite.Fail(err.Error())
+ }
+
+ // request should be blocked now
+ blocked, err := federator.Blocked(ctxWithOtherInvolvedIRIs, actorIRIs)
+ suite.NoError(err)
+ suite.True(blocked)
+}
+
+func (suite *FederatingProtocolTestSuite) TestBlocked3() {
+ fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1)
+ tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db, fedWorker)
+ federator := federation.NewFederator(suite.db, testrig.NewTestFederatingDB(suite.db, fedWorker), tc, suite.tc, testrig.NewTestMediaManager(suite.db, suite.storage))
+
+ sendingAccount := suite.testAccounts["remote_account_1"]
+ inboxAccount := suite.testAccounts["local_account_1"]
+ ccedAccount := suite.testAccounts["remote_account_2"]
+
+ otherInvolvedIRIs := []*url.URL{
+ testrig.URLMustParse(ccedAccount.URI),
+ }
+ actorIRIs := []*url.URL{
+ testrig.URLMustParse(sendingAccount.URI),
+ }
+
+ ctx := context.Background()
+ ctxWithReceivingAccount := context.WithValue(ctx, ap.ContextReceivingAccount, inboxAccount)
+ ctxWithRequestingAccount := context.WithValue(ctxWithReceivingAccount, ap.ContextRequestingAccount, sendingAccount)
+ ctxWithOtherInvolvedIRIs := context.WithValue(ctxWithRequestingAccount, ap.ContextOtherInvolvedIRIs, otherInvolvedIRIs)
+
+ // insert a block from inboxAccount targeting CCed account
+ if err := suite.db.Put(context.Background(), >smodel.Block{
+ ID: "01G3KBEMJD4VQ2D615MPV7KTRD",
+ URI: "whatever",
+ AccountID: inboxAccount.ID,
+ TargetAccountID: ccedAccount.ID,
+ }); err != nil {
+ suite.Fail(err.Error())
+ }
+
+ blocked, err := federator.Blocked(ctxWithOtherInvolvedIRIs, actorIRIs)
+ suite.NoError(err)
+ suite.True(blocked)
+}
+
+func (suite *FederatingProtocolTestSuite) TestBlocked4() {
+ fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1)
+ tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db, fedWorker)
+ federator := federation.NewFederator(suite.db, testrig.NewTestFederatingDB(suite.db, fedWorker), tc, suite.tc, testrig.NewTestMediaManager(suite.db, suite.storage))
+
+ sendingAccount := suite.testAccounts["remote_account_1"]
+ inboxAccount := suite.testAccounts["local_account_1"]
+ repliedStatus := suite.testStatuses["local_account_2_status_1"]
+
+ otherInvolvedIRIs := []*url.URL{
+ testrig.URLMustParse(repliedStatus.URI), // this status is involved because the hypothetical activity is a reply to this status
+ }
+ actorIRIs := []*url.URL{
+ testrig.URLMustParse(sendingAccount.URI),
+ }
+
+ ctx := context.Background()
+ ctxWithReceivingAccount := context.WithValue(ctx, ap.ContextReceivingAccount, inboxAccount)
+ ctxWithRequestingAccount := context.WithValue(ctxWithReceivingAccount, ap.ContextRequestingAccount, sendingAccount)
+ ctxWithOtherInvolvedIRIs := context.WithValue(ctxWithRequestingAccount, ap.ContextOtherInvolvedIRIs, otherInvolvedIRIs)
+
+ // local account 2 (replied status account) blocks sending account already so we don't need to add a block here
+
+ blocked, err := federator.Blocked(ctxWithOtherInvolvedIRIs, actorIRIs)
+ suite.NoError(err)
+ suite.True(blocked)
+}
+
func TestFederatingProtocolTestSuite(t *testing.T) {
suite.Run(t, new(FederatingProtocolTestSuite))
}
diff --git a/internal/federation/federator_test.go b/internal/federation/federator_test.go
@@ -34,6 +34,7 @@ type FederatorStandardTestSuite struct {
storage *kv.KVStore
tc typeutils.TypeConverter
testAccounts map[string]*gtsmodel.Account
+ testStatuses map[string]*gtsmodel.Status
testActivities map[string]testrig.ActivityWithSignature
}
@@ -43,6 +44,7 @@ func (suite *FederatorStandardTestSuite) SetupSuite() {
suite.storage = testrig.NewTestStorage()
suite.tc = testrig.NewTestTypeConverter(suite.db)
suite.testAccounts = testrig.NewTestAccounts()
+ suite.testStatuses = testrig.NewTestStatuses()
}
func (suite *FederatorStandardTestSuite) SetupTest() {
diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go
@@ -393,9 +393,9 @@ func (c *converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.A
if s.InReplyToID != "" {
// fetch the replied status if we don't have it on hand already
if s.InReplyTo == nil {
- rs := >smodel.Status{}
- if err := c.db.GetByID(ctx, s.InReplyToID, rs); err != nil {
- return nil, fmt.Errorf("StatusToAS: error retrieving replied-to status from db: %s", err)
+ rs, err := c.db.GetStatusByID(ctx, s.InReplyToID)
+ if err != nil {
+ return nil, fmt.Errorf("StatusToAS: error getting replied to status %s: %s", s.InReplyToID, err)
}
s.InReplyTo = rs
}
diff --git a/internal/util/unique.go b/internal/util/unique.go
@@ -18,6 +18,8 @@
package util
+import "net/url"
+
// UniqueStrings returns a deduplicated version of a given string slice.
func UniqueStrings(s []string) []string {
keys := make(map[string]bool, len(s))
@@ -30,3 +32,16 @@ func UniqueStrings(s []string) []string {
}
return list
}
+
+// UniqueURIs returns a deduplicated version of a given *url.URL slice.
+func UniqueURIs(s []*url.URL) []*url.URL {
+ keys := make(map[string]bool, len(s))
+ list := []*url.URL{}
+ for _, entry := range s {
+ if _, value := keys[entry.String()]; !value {
+ keys[entry.String()] = true
+ list = append(list, entry)
+ }
+ }
+ return list
+}
diff --git a/testrig/testmodels.go b/testrig/testmodels.go
@@ -1601,6 +1601,30 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit
dmForZork)
createDmForZorkSig, createDmForZorkDigest, creatDmForZorkDate := GetSignatureForActivity(createDmForZork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI))
+ replyToTurtle := NewAPNote(
+ URLMustParse("http://fossbros-anonymous.io/users/foss_satan/statuses/2f1195a6-5cb0-4475-adf5-92ab9a0147fe"),
+ URLMustParse("http://fossbros-anonymous.io/@foss_satan/2f1195a6-5cb0-4475-adf5-92ab9a0147fe"),
+ time.Now(),
+ "@1happyturtle@localhost:8080 u suck lol",
+ "",
+ URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
+ []*url.URL{URLMustParse("http://fossbros-anonymous.io/users/foss_satan/followers")},
+ []*url.URL{URLMustParse("http://localhost:8080/users/1happyturtle")},
+ false,
+ []vocab.ActivityStreamsMention{newAPMention(
+ URLMustParse("http://localhost:8080/users/1happyturtle"),
+ "@1happyturtle@localhost:8080",
+ )},
+ nil,
+ )
+ createReplyToTurtle := WrapAPNoteInCreate(
+ URLMustParse("http://fossbros-anonymous.io/users/foss_satan/statuses/2f1195a6-5cb0-4475-adf5-92ab9a0147fe"),
+ URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
+ time.Now(),
+ replyToTurtle)
+ createReplyToTurtleForZorkSig, createReplyToTurtleForZorkDigest, createReplyToTurtleForZorkDate := GetSignatureForActivity(createReplyToTurtle, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI))
+ createReplyToTurtleForTurtleSig, createReplyToTurtleForTurtleDigest, createReplyToTurtleForTurtleDate := GetSignatureForActivity(createReplyToTurtle, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_2"].InboxURI))
+
forwardedMessage := NewAPNote(
URLMustParse("http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1"),
URLMustParse("http://example.org/@some_user/afaba698-5740-4e32-a702-af61aa543bc1"),
@@ -1628,6 +1652,18 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit
DigestHeader: createDmForZorkDigest,
DateHeader: creatDmForZorkDate,
},
+ "reply_to_turtle_for_zork": {
+ Activity: createReplyToTurtle,
+ SignatureHeader: createReplyToTurtleForZorkSig,
+ DigestHeader: createReplyToTurtleForZorkDigest,
+ DateHeader: createReplyToTurtleForZorkDate,
+ },
+ "reply_to_turtle_for_turtle": {
+ Activity: createReplyToTurtle,
+ SignatureHeader: createReplyToTurtleForTurtleSig,
+ DigestHeader: createReplyToTurtleForTurtleDigest,
+ DateHeader: createReplyToTurtleForTurtleDate,
+ },
"forwarded_message": {
Activity: createForwardedMessage,
SignatureHeader: createForwardedMessageSig,