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(), >smodel.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(), >smodel.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 }