[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
This commit is contained in:
tobi 2022-05-23 11:46:50 +02:00 committed by GitHub
parent d6abe105b3
commit 469da93678
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 381 additions and 52 deletions

View file

@ -22,20 +22,16 @@
type ContextKey string type ContextKey string
const ( 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 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. // 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. // This will often be the actor of the instance that's posting the request.
ContextRequestingAccount ContextKey = "requestingAccount" ContextRequestingAccount ContextKey = "requestingAccount"
// ContextRequestingActorIRI can be used to set and retrieve the actor of an incoming federation request. // ContextOtherInvolvedIRIs can be used to set and retrieve a slice of all IRIs that are 'involved' in an Activity without being
// This will usually be the owner of whatever activity is being posted. // the receivingAccount or the requestingAccount. In other words, people or notes who are CC'ed or Replied To by an Activity.
ContextRequestingActorIRI ContextKey = "requestingActorIRI" ContextOtherInvolvedIRIs ContextKey = "otherInvolvedIRIs"
// ContextRequestingPublicKeyVerifier can be used to set and retrieve the public key verifier of an incoming federation request. // ContextRequestingPublicKeyVerifier can be used to set and retrieve the public key verifier of an incoming federation request.
ContextRequestingPublicKeyVerifier ContextKey = "requestingPublicKeyVerifier" ContextRequestingPublicKeyVerifier ContextKey = "requestingPublicKeyVerifier"
// ContextRequestingPublicKeySignature can be used to set and retrieve the value of the signature header of an incoming federation request. // ContextRequestingPublicKeySignature can be used to set and retrieve the value of the signature header of an incoming federation request.
ContextRequestingPublicKeySignature ContextKey = "requestingPublicKeySignature" 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"
) )

View file

@ -140,6 +140,11 @@ type Addressable interface {
WithCC 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. // CollectionPageable represents the minimum interface for an activitystreams 'CollectionPage' object.
type CollectionPageable interface { type CollectionPageable interface {
WithJSONLDId WithJSONLDId

View file

@ -23,6 +23,8 @@
"net/url" "net/url"
"strings" "strings"
"github.com/spf13/viper"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util" "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) { 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 return false, nil
} }

View file

@ -33,6 +33,7 @@
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/uris" "github.com/superseriousbusiness/gotosocial/internal/uris"
"github.com/superseriousbusiness/gotosocial/internal/util"
) )
/* /*
@ -62,19 +63,60 @@
// write a response to the ResponseWriter as is expected that the caller // write a response to the ResponseWriter as is expected that the caller
// to PostInbox will do so when handling the error. // 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) { func (f *federator) PostInboxRequestBodyHook(ctx context.Context, r *http.Request, activity pub.Activity) (context.Context, error) {
l := logrus.WithFields(logrus.Fields{ // extract any other IRIs involved in this activity
"func": "PostInboxRequestBodyHook", otherInvolvedIRIs := []*url.URL{}
"useragent": r.UserAgent(),
"url": r.URL.String(),
})
if activity == nil { // check if the Activity itself has an 'inReplyTo'
err := errors.New("nil activity in PostInboxRequestBodyHook") if replyToable, ok := activity.(ap.ReplyToable); ok {
l.Debug(err) if inReplyToURI := ap.ExtractInReplyToURI(replyToable); inReplyToURI != nil {
return nil, err otherInvolvedIRIs = append(otherInvolvedIRIs, inReplyToURI)
} }
// set the activity on the context for use later on }
return context.WithValue(ctx, ap.ContextActivity, activity), nil
// 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...)
}
}
// 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 // 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) 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) receivingAccountI := ctx.Value(ap.ContextReceivingAccount)
receivingAccount, ok := receivingAccountI.(*gtsmodel.Account) receivingAccount, ok := receivingAccountI.(*gtsmodel.Account)
if !ok { if !ok {
l.Errorf("receiving account not set on request context") 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") return false, errors.New("receiving account not set on request context, so couldn't determine blocks")
} }
requestingAccountI := ctx.Value(ap.ContextRequestingAccount)
blocked, err := f.db.AreURIsBlocked(ctx, actorIRIs) 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 { 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 { if blocked {
return blocked, nil return blocked, nil
} }
for _, uri := range actorIRIs { // get account IDs for other involved accounts
requestingAccount, err := f.db.GetAccountByURI(ctx, uri.String()) var involvedAccountIDs []string
if err != nil { for _, iri := range otherInvolvedIRIs {
if err == db.ErrNoEntries { var involvedAccountID string
// we don't have an entry for this account so it's not blocked if involvedStatus, err := f.db.GetStatusByURI(ctx, iri.String()); err == nil {
// TODO: allow a different default to be set for this behavior involvedAccountID = involvedStatus.AccountID
l.Tracef("no entry for account with URI %s so it can't be blocked", uri) } else if involvedAccount, err := f.db.GetAccountByURI(ctx, iri.String()); err == nil {
continue involvedAccountID = involvedAccount.ID
}
return false, fmt.Errorf("error getting account with uri %s: %s", uri.String(), err)
} }
blocked, err = f.db.IsBlocked(ctx, receivingAccount.ID, requestingAccount.ID, false) 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 != 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 { if blocked {
l.Tracef("local account %s blocks account with uri %s", receivingAccount.Username, uri) return blocked, nil
return true, nil }
// 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 user-level otherInvolvedIRI blocks: %s", err)
}
if blocked {
return blocked, nil
} }
} }

View file

@ -22,11 +22,11 @@
"context" "context"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"testing" "testing"
"github.com/go-fed/httpsig" "github.com/go-fed/httpsig"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/concurrency" "github.com/superseriousbusiness/gotosocial/internal/concurrency"
"github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/federation"
@ -39,8 +39,7 @@ type FederatingProtocolTestSuite struct {
FederatorStandardTestSuite FederatorStandardTestSuite
} }
// make sure PostInboxRequestBodyHook properly sets the inbox username and activity on the context func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHook1() {
func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHook() {
// the activity we're gonna use // the activity we're gonna use
activity := suite.testActivities["dm_for_zork"] activity := suite.testActivities["dm_for_zork"]
@ -63,13 +62,82 @@ func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHook() {
suite.NoError(err) suite.NoError(err)
suite.NotNil(newContext) suite.NotNil(newContext)
// activity should be set on context now involvedIRIsI := newContext.Value(ap.ContextOtherInvolvedIRIs)
activityI := newContext.Value(ap.ContextActivity) involvedIRIs, ok := involvedIRIsI.([]*url.URL)
suite.NotNil(activityI) if !ok {
returnedActivity, ok := activityI.(pub.Activity) suite.FailNow("couldn't get involved IRIs from context")
suite.True(ok) }
suite.NotNil(returnedActivity)
suite.EqualValues(activity.Activity, returnedActivity) 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() { func (suite *FederatingProtocolTestSuite) TestAuthenticatePostInbox() {
@ -97,8 +165,7 @@ func (suite *FederatingProtocolTestSuite) TestAuthenticatePostInbox() {
// by the time AuthenticatePostInbox is called, PostInboxRequestBodyHook should have already been called, // 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: // which should have set the account and username onto the request. We can replicate that behavior here:
ctxWithAccount := context.WithValue(ctx, ap.ContextReceivingAccount, inboxAccount) ctxWithAccount := context.WithValue(ctx, ap.ContextReceivingAccount, inboxAccount)
ctxWithActivity := context.WithValue(ctxWithAccount, ap.ContextActivity, activity) ctxWithVerifier := context.WithValue(ctxWithAccount, ap.ContextRequestingPublicKeyVerifier, verifier)
ctxWithVerifier := context.WithValue(ctxWithActivity, ap.ContextRequestingPublicKeyVerifier, verifier)
ctxWithSignature := context.WithValue(ctxWithVerifier, ap.ContextRequestingPublicKeySignature, activity.SignatureHeader) ctxWithSignature := context.WithValue(ctxWithVerifier, ap.ContextRequestingPublicKeySignature, activity.SignatureHeader)
// we can pass this recorder as a writer and read it back after // 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) 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(), &gtsmodel.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(), &gtsmodel.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) { func TestFederatingProtocolTestSuite(t *testing.T) {
suite.Run(t, new(FederatingProtocolTestSuite)) suite.Run(t, new(FederatingProtocolTestSuite))
} }

View file

@ -34,6 +34,7 @@ type FederatorStandardTestSuite struct {
storage *kv.KVStore storage *kv.KVStore
tc typeutils.TypeConverter tc typeutils.TypeConverter
testAccounts map[string]*gtsmodel.Account testAccounts map[string]*gtsmodel.Account
testStatuses map[string]*gtsmodel.Status
testActivities map[string]testrig.ActivityWithSignature testActivities map[string]testrig.ActivityWithSignature
} }
@ -43,6 +44,7 @@ func (suite *FederatorStandardTestSuite) SetupSuite() {
suite.storage = testrig.NewTestStorage() suite.storage = testrig.NewTestStorage()
suite.tc = testrig.NewTestTypeConverter(suite.db) suite.tc = testrig.NewTestTypeConverter(suite.db)
suite.testAccounts = testrig.NewTestAccounts() suite.testAccounts = testrig.NewTestAccounts()
suite.testStatuses = testrig.NewTestStatuses()
} }
func (suite *FederatorStandardTestSuite) SetupTest() { func (suite *FederatorStandardTestSuite) SetupTest() {

View file

@ -393,9 +393,9 @@ func (c *converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.A
if s.InReplyToID != "" { if s.InReplyToID != "" {
// fetch the replied status if we don't have it on hand already // fetch the replied status if we don't have it on hand already
if s.InReplyTo == nil { if s.InReplyTo == nil {
rs := &gtsmodel.Status{} rs, err := c.db.GetStatusByID(ctx, s.InReplyToID)
if err := c.db.GetByID(ctx, s.InReplyToID, rs); err != nil { if err != nil {
return nil, fmt.Errorf("StatusToAS: error retrieving replied-to status from db: %s", err) return nil, fmt.Errorf("StatusToAS: error getting replied to status %s: %s", s.InReplyToID, err)
} }
s.InReplyTo = rs s.InReplyTo = rs
} }

View file

@ -18,6 +18,8 @@
package util package util
import "net/url"
// UniqueStrings returns a deduplicated version of a given string slice. // UniqueStrings returns a deduplicated version of a given string slice.
func UniqueStrings(s []string) []string { func UniqueStrings(s []string) []string {
keys := make(map[string]bool, len(s)) keys := make(map[string]bool, len(s))
@ -30,3 +32,16 @@ func UniqueStrings(s []string) []string {
} }
return list 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
}

View file

@ -1601,6 +1601,30 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit
dmForZork) dmForZork)
createDmForZorkSig, createDmForZorkDigest, creatDmForZorkDate := GetSignatureForActivity(createDmForZork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI)) 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( forwardedMessage := NewAPNote(
URLMustParse("http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1"), URLMustParse("http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1"),
URLMustParse("http://example.org/@some_user/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, DigestHeader: createDmForZorkDigest,
DateHeader: creatDmForZorkDate, 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": { "forwarded_message": {
Activity: createForwardedMessage, Activity: createForwardedMessage,
SignatureHeader: createForwardedMessageSig, SignatureHeader: createForwardedMessageSig,