gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

federatingprotocol_test.go (13122B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package federation_test
     19 
     20 import (
     21 	"bytes"
     22 	"context"
     23 	"encoding/json"
     24 	"io"
     25 	"net/http"
     26 	"net/http/httptest"
     27 	"net/url"
     28 	"testing"
     29 
     30 	"github.com/go-fed/httpsig"
     31 	"github.com/stretchr/testify/suite"
     32 	"github.com/superseriousbusiness/gotosocial/internal/ap"
     33 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
     34 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
     35 	"github.com/superseriousbusiness/gotosocial/testrig"
     36 )
     37 
     38 type FederatingProtocolTestSuite struct {
     39 	FederatorStandardTestSuite
     40 }
     41 
     42 func (suite *FederatingProtocolTestSuite) postInboxRequestBodyHook(
     43 	ctx context.Context,
     44 	receivingAccount *gtsmodel.Account,
     45 	activity testrig.ActivityWithSignature,
     46 ) context.Context {
     47 	raw, err := ap.Serialize(activity.Activity)
     48 	if err != nil {
     49 		suite.FailNow(err.Error())
     50 	}
     51 
     52 	b, err := json.Marshal(raw)
     53 	if err != nil {
     54 		suite.FailNow(err.Error())
     55 	}
     56 	suite.NoError(err)
     57 	request := httptest.NewRequest(http.MethodPost, receivingAccount.InboxURI, bytes.NewBuffer(b))
     58 	request.Header.Set("Signature", activity.SignatureHeader)
     59 	request.Header.Set("Date", activity.DateHeader)
     60 	request.Header.Set("Digest", activity.DigestHeader)
     61 
     62 	newContext, err := suite.federator.PostInboxRequestBodyHook(ctx, request, activity.Activity)
     63 	if err != nil {
     64 		suite.FailNow(err.Error())
     65 	}
     66 
     67 	return newContext
     68 }
     69 
     70 func (suite *FederatingProtocolTestSuite) authenticatePostInbox(
     71 	ctx context.Context,
     72 	receivingAccount *gtsmodel.Account,
     73 	activity testrig.ActivityWithSignature,
     74 ) (context.Context, bool, []byte, int) {
     75 	raw, err := ap.Serialize(activity.Activity)
     76 	if err != nil {
     77 		suite.FailNow(err.Error())
     78 	}
     79 
     80 	b, err := json.Marshal(raw)
     81 	if err != nil {
     82 		suite.FailNow(err.Error())
     83 	}
     84 
     85 	request := httptest.NewRequest(http.MethodPost, receivingAccount.InboxURI, bytes.NewBuffer(b))
     86 	request.Header.Set("Signature", activity.SignatureHeader)
     87 	request.Header.Set("Date", activity.DateHeader)
     88 	request.Header.Set("Digest", activity.DigestHeader)
     89 
     90 	verifier, err := httpsig.NewVerifier(request)
     91 	if err != nil {
     92 		suite.FailNow(err.Error())
     93 	}
     94 
     95 	ctx = gtscontext.SetReceivingAccount(ctx, receivingAccount)
     96 	ctx = gtscontext.SetHTTPSignatureVerifier(ctx, verifier)
     97 	ctx = gtscontext.SetHTTPSignature(ctx, activity.SignatureHeader)
     98 	ctx = gtscontext.SetHTTPSignaturePubKeyID(ctx, testrig.URLMustParse(verifier.KeyId()))
     99 
    100 	recorder := httptest.NewRecorder()
    101 	newContext, authed, err := suite.federator.AuthenticatePostInbox(ctx, recorder, request)
    102 	if err != nil {
    103 		suite.FailNow(err.Error())
    104 	}
    105 
    106 	res := recorder.Result()
    107 	defer res.Body.Close()
    108 
    109 	b, err = io.ReadAll(res.Body)
    110 	if err != nil {
    111 		suite.FailNow(err.Error())
    112 	}
    113 
    114 	return newContext, authed, b, res.StatusCode
    115 }
    116 
    117 func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHookDM() {
    118 	var (
    119 		receivingAccount = suite.testAccounts["local_account_1"]
    120 		activity         = suite.testActivities["dm_for_zork"]
    121 	)
    122 
    123 	ctx := suite.postInboxRequestBodyHook(
    124 		context.Background(),
    125 		receivingAccount,
    126 		activity,
    127 	)
    128 
    129 	otherIRIs := gtscontext.OtherIRIs(ctx)
    130 	otherIRIStrs := make([]string, 0, len(otherIRIs))
    131 	for _, i := range otherIRIs {
    132 		otherIRIStrs = append(otherIRIStrs, i.String())
    133 	}
    134 
    135 	suite.Equal([]string{
    136 		"http://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6/activity",
    137 		"http://localhost:8080/users/the_mighty_zork",
    138 		"http://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6",
    139 	}, otherIRIStrs)
    140 }
    141 
    142 func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHookReply() {
    143 	var (
    144 		receivingAccount = suite.testAccounts["local_account_1"]
    145 		activity         = suite.testActivities["reply_to_turtle_for_zork"]
    146 	)
    147 
    148 	ctx := suite.postInboxRequestBodyHook(
    149 		context.Background(),
    150 		receivingAccount,
    151 		activity,
    152 	)
    153 
    154 	otherIRIs := gtscontext.OtherIRIs(ctx)
    155 	otherIRIStrs := make([]string, 0, len(otherIRIs))
    156 	for _, i := range otherIRIs {
    157 		otherIRIStrs = append(otherIRIStrs, i.String())
    158 	}
    159 
    160 	suite.Equal([]string{
    161 		"http://fossbros-anonymous.io/users/foss_satan/statuses/2f1195a6-5cb0-4475-adf5-92ab9a0147fe",
    162 		"http://fossbros-anonymous.io/users/foss_satan/followers",
    163 		"http://localhost:8080/users/1happyturtle",
    164 	}, otherIRIStrs)
    165 }
    166 
    167 func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHookReplyToReply() {
    168 	var (
    169 		receivingAccount = suite.testAccounts["local_account_2"]
    170 		activity         = suite.testActivities["reply_to_turtle_for_turtle"]
    171 	)
    172 
    173 	ctx := suite.postInboxRequestBodyHook(
    174 		context.Background(),
    175 		receivingAccount,
    176 		activity,
    177 	)
    178 
    179 	otherIRIs := gtscontext.OtherIRIs(ctx)
    180 	otherIRIStrs := make([]string, 0, len(otherIRIs))
    181 	for _, i := range otherIRIs {
    182 		otherIRIStrs = append(otherIRIStrs, i.String())
    183 	}
    184 
    185 	suite.Equal([]string{
    186 		"http://fossbros-anonymous.io/users/foss_satan/statuses/2f1195a6-5cb0-4475-adf5-92ab9a0147fe",
    187 		"http://fossbros-anonymous.io/users/foss_satan/followers",
    188 		"http://localhost:8080/users/1happyturtle",
    189 	}, otherIRIStrs)
    190 }
    191 
    192 func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHookAnnounceForwardedToTurtle() {
    193 	var (
    194 		receivingAccount = suite.testAccounts["local_account_2"]
    195 		activity         = suite.testActivities["announce_forwarded_1_turtle"]
    196 	)
    197 
    198 	ctx := suite.postInboxRequestBodyHook(
    199 		context.Background(),
    200 		receivingAccount,
    201 		activity,
    202 	)
    203 
    204 	otherIRIs := gtscontext.OtherIRIs(ctx)
    205 	otherIRIStrs := make([]string, 0, len(otherIRIs))
    206 	for _, i := range otherIRIs {
    207 		otherIRIStrs = append(otherIRIStrs, i.String())
    208 	}
    209 
    210 	suite.Equal([]string{
    211 		"http://fossbros-anonymous.io/users/foss_satan/first_announce",
    212 		"http://example.org/users/Some_User",
    213 		"http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1",
    214 	}, otherIRIStrs)
    215 }
    216 
    217 func (suite *FederatingProtocolTestSuite) TestPostInboxRequestBodyHookAnnounceForwardedToZork() {
    218 	var (
    219 		receivingAccount = suite.testAccounts["local_account_1"]
    220 		activity         = suite.testActivities["announce_forwarded_2_zork"]
    221 	)
    222 
    223 	ctx := suite.postInboxRequestBodyHook(
    224 		context.Background(),
    225 		receivingAccount,
    226 		activity,
    227 	)
    228 
    229 	otherIRIs := gtscontext.OtherIRIs(ctx)
    230 	otherIRIStrs := make([]string, 0, len(otherIRIs))
    231 	for _, i := range otherIRIs {
    232 		otherIRIStrs = append(otherIRIStrs, i.String())
    233 	}
    234 
    235 	suite.Equal([]string{
    236 		"http://fossbros-anonymous.io/users/foss_satan/second_announce",
    237 		"http://example.org/users/Some_User",
    238 		"http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1",
    239 	}, otherIRIStrs)
    240 }
    241 
    242 func (suite *FederatingProtocolTestSuite) TestAuthenticatePostInbox() {
    243 	var (
    244 		activity         = suite.testActivities["dm_for_zork"]
    245 		receivingAccount = suite.testAccounts["local_account_1"]
    246 	)
    247 
    248 	ctx, authed, resp, code := suite.authenticatePostInbox(
    249 		context.Background(),
    250 		receivingAccount,
    251 		activity,
    252 	)
    253 
    254 	suite.NotNil(gtscontext.RequestingAccount(ctx))
    255 	suite.True(authed)
    256 	suite.Equal([]byte{}, resp)
    257 	suite.Equal(http.StatusOK, code)
    258 }
    259 
    260 func (suite *FederatingProtocolTestSuite) TestAuthenticatePostGoneWithTombstone() {
    261 	var (
    262 		activity         = suite.testActivities["delete_https://somewhere.mysterious/users/rest_in_piss#main-key"]
    263 		receivingAccount = suite.testAccounts["local_account_1"]
    264 	)
    265 
    266 	ctx, authed, resp, code := suite.authenticatePostInbox(
    267 		context.Background(),
    268 		receivingAccount,
    269 		activity,
    270 	)
    271 
    272 	// Tombstone exists for this account, should simply return accepted.
    273 	suite.Nil(gtscontext.RequestingAccount(ctx))
    274 	suite.False(authed)
    275 	suite.Equal([]byte{}, resp)
    276 	suite.Equal(http.StatusAccepted, code)
    277 }
    278 
    279 func (suite *FederatingProtocolTestSuite) TestAuthenticatePostGoneNoTombstone() {
    280 	var (
    281 		activity         = suite.testActivities["delete_https://somewhere.mysterious/users/rest_in_piss#main-key"]
    282 		receivingAccount = suite.testAccounts["local_account_1"]
    283 		testTombstone    = suite.testTombstones["https://somewhere.mysterious/users/rest_in_piss#main-key"]
    284 	)
    285 
    286 	// Delete the tombstone; it'll have to be created again.
    287 	if err := suite.state.DB.DeleteTombstone(context.Background(), testTombstone.ID); err != nil {
    288 		suite.FailNow(err.Error())
    289 	}
    290 
    291 	ctx, authed, resp, code := suite.authenticatePostInbox(
    292 		context.Background(),
    293 		receivingAccount,
    294 		activity,
    295 	)
    296 
    297 	suite.Nil(gtscontext.RequestingAccount(ctx))
    298 	suite.False(authed)
    299 	suite.Equal([]byte{}, resp)
    300 	suite.Equal(http.StatusAccepted, code)
    301 
    302 	// Tombstone should be back, baby!
    303 	exists, err := suite.state.DB.TombstoneExistsWithURI(
    304 		context.Background(),
    305 		"https://somewhere.mysterious/users/rest_in_piss#main-key",
    306 	)
    307 	suite.NoError(err)
    308 	suite.True(exists)
    309 }
    310 
    311 func (suite *FederatingProtocolTestSuite) blocked(
    312 	ctx context.Context,
    313 	receivingAccount *gtsmodel.Account,
    314 	requestingAccount *gtsmodel.Account,
    315 	otherIRIs []*url.URL,
    316 	actorIRIs []*url.URL,
    317 ) (bool, error) {
    318 	ctx = gtscontext.SetReceivingAccount(ctx, receivingAccount)
    319 	ctx = gtscontext.SetRequestingAccount(ctx, requestingAccount)
    320 	ctx = gtscontext.SetOtherIRIs(ctx, otherIRIs)
    321 	return suite.federator.Blocked(ctx, actorIRIs)
    322 }
    323 
    324 func (suite *FederatingProtocolTestSuite) TestBlockedNoProblem() {
    325 	var (
    326 		receivingAccount  = suite.testAccounts["local_account_1"]
    327 		requestingAccount = suite.testAccounts["remote_account_1"]
    328 		otherIRIs         = []*url.URL{}
    329 		actorIRIs         = []*url.URL{
    330 			testrig.URLMustParse(requestingAccount.URI),
    331 		}
    332 	)
    333 
    334 	blocked, err := suite.blocked(
    335 		context.Background(),
    336 		receivingAccount,
    337 		requestingAccount,
    338 		otherIRIs,
    339 		actorIRIs,
    340 	)
    341 
    342 	suite.NoError(err)
    343 	suite.False(blocked)
    344 }
    345 
    346 func (suite *FederatingProtocolTestSuite) TestBlockedReceiverBlocksRequester() {
    347 	var (
    348 		receivingAccount  = suite.testAccounts["local_account_1"]
    349 		requestingAccount = suite.testAccounts["remote_account_1"]
    350 		otherIRIs         = []*url.URL{}
    351 		actorIRIs         = []*url.URL{
    352 			testrig.URLMustParse(requestingAccount.URI),
    353 		}
    354 	)
    355 
    356 	// Insert a block from receivingAccount targeting requestingAccount.
    357 	if err := suite.state.DB.PutBlock(context.Background(), &gtsmodel.Block{
    358 		ID:              "01G3KBEMJD4VQ2D615MPV7KTRD",
    359 		URI:             "whatever",
    360 		AccountID:       receivingAccount.ID,
    361 		TargetAccountID: requestingAccount.ID,
    362 	}); err != nil {
    363 		suite.Fail(err.Error())
    364 	}
    365 
    366 	blocked, err := suite.blocked(
    367 		context.Background(),
    368 		receivingAccount,
    369 		requestingAccount,
    370 		otherIRIs,
    371 		actorIRIs,
    372 	)
    373 
    374 	suite.NoError(err)
    375 	suite.True(blocked)
    376 }
    377 
    378 func (suite *FederatingProtocolTestSuite) TestBlockedCCd() {
    379 	var (
    380 		receivingAccount  = suite.testAccounts["local_account_1"]
    381 		requestingAccount = suite.testAccounts["remote_account_1"]
    382 		ccedAccount       = suite.testAccounts["remote_account_2"]
    383 		otherIRIs         = []*url.URL{
    384 			testrig.URLMustParse(ccedAccount.URI),
    385 		}
    386 		actorIRIs = []*url.URL{
    387 			testrig.URLMustParse(requestingAccount.URI),
    388 		}
    389 	)
    390 
    391 	// Insert a block from receivingAccount targeting ccedAccount.
    392 	if err := suite.state.DB.PutBlock(context.Background(), &gtsmodel.Block{
    393 		ID:              "01G3KBEMJD4VQ2D615MPV7KTRD",
    394 		URI:             "whatever",
    395 		AccountID:       receivingAccount.ID,
    396 		TargetAccountID: ccedAccount.ID,
    397 	}); err != nil {
    398 		suite.Fail(err.Error())
    399 	}
    400 
    401 	blocked, err := suite.blocked(
    402 		context.Background(),
    403 		receivingAccount,
    404 		requestingAccount,
    405 		otherIRIs,
    406 		actorIRIs,
    407 	)
    408 
    409 	suite.EqualError(err, "block exists between http://localhost:8080/users/the_mighty_zork and one or more of [http://example.org/users/Some_User]")
    410 	suite.False(blocked)
    411 }
    412 
    413 func (suite *FederatingProtocolTestSuite) TestBlockedRepliedStatus() {
    414 	var (
    415 		receivingAccount  = suite.testAccounts["local_account_1"]
    416 		requestingAccount = suite.testAccounts["remote_account_1"]
    417 		repliedStatus     = suite.testStatuses["local_account_2_status_1"]
    418 		otherIRIs         = []*url.URL{
    419 			// This status is involved because the
    420 			// hypothetical activity replies to it.
    421 			testrig.URLMustParse(repliedStatus.URI),
    422 		}
    423 		actorIRIs = []*url.URL{
    424 			testrig.URLMustParse(requestingAccount.URI),
    425 		}
    426 	)
    427 
    428 	blocked, err := suite.blocked(
    429 		context.Background(),
    430 		receivingAccount,
    431 		requestingAccount,
    432 		otherIRIs,
    433 		actorIRIs,
    434 	)
    435 
    436 	suite.EqualError(err, "block exists between http://fossbros-anonymous.io/users/foss_satan and one or more of [http://localhost:8080/users/1happyturtle/statuses/01F8MHBQCBTDKN6X5VHGMMN4MA]")
    437 	suite.False(blocked)
    438 }
    439 
    440 func TestFederatingProtocolTestSuite(t *testing.T) {
    441 	suite.Run(t, new(FederatingProtocolTestSuite))
    442 }