federatingprotocol.go (20621B)
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 19 20 import ( 21 "context" 22 "errors" 23 "net/http" 24 "net/url" 25 "strings" 26 27 "codeberg.org/gruf/go-kv" 28 "github.com/superseriousbusiness/activity/pub" 29 "github.com/superseriousbusiness/activity/streams" 30 "github.com/superseriousbusiness/activity/streams/vocab" 31 "github.com/superseriousbusiness/gotosocial/internal/ap" 32 "github.com/superseriousbusiness/gotosocial/internal/config" 33 "github.com/superseriousbusiness/gotosocial/internal/db" 34 "github.com/superseriousbusiness/gotosocial/internal/gtscontext" 35 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 36 "github.com/superseriousbusiness/gotosocial/internal/log" 37 "github.com/superseriousbusiness/gotosocial/internal/uris" 38 "github.com/superseriousbusiness/gotosocial/internal/util" 39 ) 40 41 type errOtherIRIBlocked struct { 42 account string 43 domainBlock bool 44 iriStrs []string 45 } 46 47 func (e errOtherIRIBlocked) Error() string { 48 iriStrsNice := "[" + strings.Join(e.iriStrs, ", ") + "]" 49 if e.domainBlock { 50 return "domain block exists for one or more of " + iriStrsNice 51 } 52 return "block exists between " + e.account + " and one or more of " + iriStrsNice 53 } 54 55 func newErrOtherIRIBlocked( 56 account string, 57 domainBlock bool, 58 otherIRIs []*url.URL, 59 ) error { 60 e := errOtherIRIBlocked{ 61 account: account, 62 domainBlock: domainBlock, 63 iriStrs: make([]string, 0, len(otherIRIs)), 64 } 65 66 for _, iri := range otherIRIs { 67 e.iriStrs = append(e.iriStrs, iri.String()) 68 } 69 70 return e 71 } 72 73 /* 74 GO FED FEDERATING PROTOCOL INTERFACE 75 FederatingProtocol contains behaviors an application needs to satisfy for the 76 full ActivityPub S2S implementation to be supported by this library. 77 It is only required if the client application wants to support the server-to- 78 server, or federating, protocol. 79 It is passed to the library as a dependency injection from the client 80 application. 81 */ 82 83 // PostInboxRequestBodyHook callback after parsing the request body for a 84 // federated request to the Actor's inbox. 85 // 86 // Can be used to set contextual information based on the Activity received. 87 // 88 // Warning: Neither authentication nor authorization has taken place at 89 // this time. Doing anything beyond setting contextual information is 90 // strongly discouraged. 91 // 92 // If an error is returned, it is passed back to the caller of PostInbox. 93 // In this case, the DelegateActor implementation must not write a response 94 // to the ResponseWriter as is expected that the caller to PostInbox will 95 // do so when handling the error. 96 func (f *federator) PostInboxRequestBodyHook(ctx context.Context, r *http.Request, activity pub.Activity) (context.Context, error) { 97 // Extract any other IRIs involved in this activity. 98 otherIRIs := []*url.URL{} 99 100 // Get the ID of the Activity itslf. 101 activityID, err := pub.GetId(activity) 102 if err == nil { 103 otherIRIs = append(otherIRIs, activityID) 104 } 105 106 // Check if the Activity has an 'inReplyTo'. 107 if replyToable, ok := activity.(ap.ReplyToable); ok { 108 if inReplyToURI := ap.ExtractInReplyToURI(replyToable); inReplyToURI != nil { 109 otherIRIs = append(otherIRIs, inReplyToURI) 110 } 111 } 112 113 // Check for TO and CC URIs on the Activity. 114 if addressable, ok := activity.(ap.Addressable); ok { 115 otherIRIs = append(otherIRIs, ap.ExtractToURIs(addressable)...) 116 otherIRIs = append(otherIRIs, ap.ExtractCcURIs(addressable)...) 117 } 118 119 // Now perform the same checks, but for the Object(s) of the Activity. 120 objectProp := activity.GetActivityStreamsObject() 121 for iter := objectProp.Begin(); iter != objectProp.End(); iter = iter.Next() { 122 if iter.IsIRI() { 123 otherIRIs = append(otherIRIs, iter.GetIRI()) 124 continue 125 } 126 127 t := iter.GetType() 128 if t == nil { 129 continue 130 } 131 132 objectID, err := pub.GetId(t) 133 if err == nil { 134 otherIRIs = append(otherIRIs, objectID) 135 } 136 137 if replyToable, ok := t.(ap.ReplyToable); ok { 138 if inReplyToURI := ap.ExtractInReplyToURI(replyToable); inReplyToURI != nil { 139 otherIRIs = append(otherIRIs, inReplyToURI) 140 } 141 } 142 143 if addressable, ok := t.(ap.Addressable); ok { 144 otherIRIs = append(otherIRIs, ap.ExtractToURIs(addressable)...) 145 otherIRIs = append(otherIRIs, ap.ExtractCcURIs(addressable)...) 146 } 147 } 148 149 // Clean any instances of the public URI, since 150 // we don't care about that in this context. 151 otherIRIs = func(iris []*url.URL) []*url.URL { 152 np := make([]*url.URL, 0, len(iris)) 153 154 for _, i := range iris { 155 if !pub.IsPublic(i.String()) { 156 np = append(np, i) 157 } 158 } 159 160 return np 161 }(otherIRIs) 162 163 // OtherIRIs will likely contain some 164 // duplicate entries now, so remove them. 165 otherIRIs = util.UniqueURIs(otherIRIs) 166 167 // Finished, set other IRIs on the context 168 // so they can be checked for blocks later. 169 ctx = gtscontext.SetOtherIRIs(ctx, otherIRIs) 170 return ctx, nil 171 } 172 173 // AuthenticatePostInbox delegates the authentication of a POST to an 174 // inbox. 175 // 176 // If an error is returned, it is passed back to the caller of 177 // PostInbox. In this case, the implementation must not write a 178 // response to the ResponseWriter as is expected that the client will 179 // do so when handling the error. The 'authenticated' is ignored. 180 // 181 // If no error is returned, but authentication or authorization fails, 182 // then authenticated must be false and error nil. It is expected that 183 // the implementation handles writing to the ResponseWriter in this 184 // case. 185 // 186 // Finally, if the authentication and authorization succeeds, then 187 // authenticated must be true and error nil. The request will continue 188 // to be processed. 189 func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) { 190 log.Tracef(ctx, "received request to authenticate inbox %s", r.URL.String()) 191 192 // Ensure this is an inbox path, and fetch the inbox owner 193 // account by parsing username from `/users/{username}/inbox`. 194 username, err := uris.ParseInboxPath(r.URL) 195 if err != nil { 196 err = gtserror.Newf("could not parse %s as inbox path: %w", r.URL.String(), err) 197 return nil, false, err 198 } 199 200 if username == "" { 201 err = gtserror.New("inbox username was empty") 202 return nil, false, err 203 } 204 205 receivingAccount, err := f.db.GetAccountByUsernameDomain(ctx, username, "") 206 if err != nil { 207 err = gtserror.Newf("could not fetch receiving account %s: %w", username, err) 208 return nil, false, err 209 } 210 211 // Check who's trying to deliver to us by inspecting the http signature. 212 pubKeyOwner, errWithCode := f.AuthenticateFederatedRequest(ctx, receivingAccount.Username) 213 if errWithCode != nil { 214 switch errWithCode.Code() { 215 case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest: 216 // If codes 400, 401, or 403, obey the go-fed 217 // interface by writing the header and bailing. 218 w.WriteHeader(errWithCode.Code()) 219 return ctx, false, nil 220 case http.StatusGone: 221 // If the requesting account's key has gone 222 // (410) then likely inbox post was a delete. 223 // 224 // We can just write 202 and leave: we didn't 225 // know about the account anyway, so we can't 226 // do any further processing. 227 w.WriteHeader(http.StatusAccepted) 228 return ctx, false, nil 229 default: 230 // Proper error. 231 return ctx, false, err 232 } 233 } 234 235 // Authentication has passed, check if we need to create a 236 // new instance entry for the Host of the requesting account. 237 if _, err := f.db.GetInstance(ctx, pubKeyOwner.Host); err != nil { 238 if !errors.Is(err, db.ErrNoEntries) { 239 // There's been an actual error. 240 err = gtserror.Newf("error getting instance %s: %w", pubKeyOwner.Host, err) 241 return ctx, false, err 242 } 243 244 // We don't have an entry for this 245 // instance yet; go dereference it. 246 instance, err := f.GetRemoteInstance( 247 gtscontext.SetFastFail(ctx), 248 username, 249 &url.URL{ 250 Scheme: pubKeyOwner.Scheme, 251 Host: pubKeyOwner.Host, 252 }, 253 ) 254 if err != nil { 255 err = gtserror.Newf("error dereferencing instance %s: %w", pubKeyOwner.Host, err) 256 return nil, false, err 257 } 258 259 if err := f.db.Put(ctx, instance); err != nil && !errors.Is(err, db.ErrAlreadyExists) { 260 err = gtserror.Newf("error inserting instance entry for %s: %w", pubKeyOwner.Host, err) 261 return nil, false, err 262 } 263 } 264 265 // We know the public key owner URI now, so we can 266 // dereference the remote account (or just get it 267 // from the db if we already have it). 268 requestingAccount, _, err := f.GetAccountByURI( 269 gtscontext.SetFastFail(ctx), 270 username, 271 pubKeyOwner, 272 ) 273 if err != nil { 274 if gtserror.StatusCode(err) == http.StatusGone { 275 // This is the same case as the http.StatusGone check above. 276 // It can happen here and not there because there's a race 277 // where the sending server starts sending account deletion 278 // notifications out, we start processing, the request above 279 // succeeds, and *then* the profile is removed and starts 280 // returning 410 Gone, at which point _this_ request fails. 281 w.WriteHeader(http.StatusAccepted) 282 return ctx, false, nil 283 } 284 285 err = gtserror.Newf("couldn't get requesting account %s: %w", pubKeyOwner, err) 286 return nil, false, err 287 } 288 289 // We have everything we need now, set the requesting 290 // and receiving accounts on the context for later use. 291 ctx = gtscontext.SetRequestingAccount(ctx, requestingAccount) 292 ctx = gtscontext.SetReceivingAccount(ctx, receivingAccount) 293 return ctx, true, nil 294 } 295 296 // Blocked should determine whether to permit a set of actors given by 297 // their ids are able to interact with this particular end user due to 298 // being blocked or other application-specific logic. 299 func (f *federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, error) { 300 // Fetch relevant items from request context. 301 // These should have been set further up the flow. 302 receivingAccount := gtscontext.ReceivingAccount(ctx) 303 if receivingAccount == nil { 304 err := gtserror.New("couldn't determine blocks (receiving account not set on request context)") 305 return false, err 306 } 307 308 requestingAccount := gtscontext.RequestingAccount(ctx) 309 if requestingAccount == nil { 310 err := gtserror.New("couldn't determine blocks (requesting account not set on request context)") 311 return false, err 312 } 313 314 otherIRIs := gtscontext.OtherIRIs(ctx) 315 if otherIRIs == nil { 316 err := gtserror.New("couldn't determine blocks (otherIRIs not set on request context)") 317 return false, err 318 } 319 320 l := log. 321 WithContext(ctx). 322 WithFields(kv.Fields{ 323 {"actorIRIs", actorIRIs}, 324 {"receivingAccount", receivingAccount.URI}, 325 {"requestingAccount", requestingAccount.URI}, 326 {"otherIRIs", otherIRIs}, 327 }...) 328 l.Trace("checking blocks") 329 330 // Start broad by checking domain-level blocks first for 331 // the given actor IRIs; if any of them are domain blocked 332 // then we can save some work. 333 blocked, err := f.db.AreURIsBlocked(ctx, actorIRIs) 334 if err != nil { 335 err = gtserror.Newf("error checking domain blocks of actorIRIs: %w", err) 336 return false, err 337 } 338 339 if blocked { 340 l.Trace("one or more actorIRIs are domain blocked") 341 return blocked, nil 342 } 343 344 // Now user level blocks. Receiver should not block requester. 345 blocked, err = f.db.IsBlocked(ctx, receivingAccount.ID, requestingAccount.ID) 346 if err != nil { 347 err = gtserror.Newf("db error checking block between receiver and requester: %w", err) 348 return false, err 349 } 350 351 if blocked { 352 l.Trace("receiving account blocks requesting account") 353 return blocked, nil 354 } 355 356 // We've established that no blocks exist between directly 357 // involved actors, but what about IRIs of other actors and 358 // objects which are tangentially involved in the activity 359 // (ie., replied to, boosted)? 360 // 361 // If one or more of these other IRIs is domain blocked, or 362 // blocked by the receiving account, this shouldn't return 363 // blocked=true to send a 403, since that would be rather 364 // silly behavior. Instead, we should indicate to the caller 365 // that we should stop processing the activity and just write 366 // 202 Accepted instead. 367 // 368 // For this, we can use the errOtherIRIBlocked type, which 369 // will be checked for 370 371 // Check high-level domain blocks first. 372 blocked, err = f.db.AreURIsBlocked(ctx, otherIRIs) 373 if err != nil { 374 err := gtserror.Newf("error checking domain block of otherIRIs: %w", err) 375 return false, err 376 } 377 378 if blocked { 379 err := newErrOtherIRIBlocked(receivingAccount.URI, true, otherIRIs) 380 l.Trace(err.Error()) 381 return false, err 382 } 383 384 // For each other IRI, check whether the IRI points to an 385 // account or a status, and try to get (an) accountID(s) 386 // from it to do further checks on. 387 // 388 // We use a map for this instead of a slice in order to 389 // deduplicate entries and avoid doing the same check twice. 390 // The map value is the host of the otherIRI. 391 accountIDs := make(map[string]string, len(otherIRIs)) 392 for _, iri := range otherIRIs { 393 // Assemble iri string just once. 394 iriStr := iri.String() 395 396 account, err := f.db.GetAccountByURI( 397 // We're on a hot path, fetch bare minimum. 398 gtscontext.SetBarebones(ctx), 399 iriStr, 400 ) 401 if err != nil && !errors.Is(err, db.ErrNoEntries) { 402 // Real db error. 403 err = gtserror.Newf("db error trying to get %s as account: %w", iriStr, err) 404 return false, err 405 } else if err == nil { 406 // IRI is for an account. 407 accountIDs[account.ID] = iri.Host 408 continue 409 } 410 411 status, err := f.db.GetStatusByURI( 412 // We're on a hot path, fetch bare minimum. 413 gtscontext.SetBarebones(ctx), 414 iriStr, 415 ) 416 if err != nil && !errors.Is(err, db.ErrNoEntries) { 417 // Real db error. 418 err = gtserror.Newf("db error trying to get %s as status: %w", iriStr, err) 419 return false, err 420 } else if err == nil { 421 // IRI is for a status. 422 accountIDs[status.AccountID] = iri.Host 423 continue 424 } 425 } 426 427 // Get our own host value just once outside the loop. 428 ourHost := config.GetHost() 429 430 for accountID, iriHost := range accountIDs { 431 // Receiver shouldn't block other IRI owner. 432 // 433 // This check protects against cases where someone on our 434 // instance is receiving a boost from someone they don't 435 // block, but the boost target is the status of an account 436 // they DO have blocked, or the boosted status mentions an 437 // account they have blocked. In this case, it's v. unlikely 438 // they care to see the boost in their timeline, so there's 439 // no point in us processing it. 440 blocked, err = f.db.IsBlocked(ctx, receivingAccount.ID, accountID) 441 if err != nil { 442 err = gtserror.Newf("db error checking block between receiver and other account: %w", err) 443 return false, err 444 } 445 446 if blocked { 447 l.Trace("receiving account blocks one or more otherIRIs") 448 err := newErrOtherIRIBlocked(receivingAccount.URI, false, otherIRIs) 449 return false, err 450 } 451 452 // If other account is from our instance (indicated by the 453 // host of the URI stored in the map), ensure they don't block 454 // the requester. 455 // 456 // This check protects against cases where one of our users 457 // might be mentioned by the requesting account, and therefore 458 // appear in otherIRIs, but the activity itself has been sent 459 // to a different account on our instance. In other words, two 460 // accounts are gossiping about + trying to tag a third account 461 // who has one or the other of them blocked. 462 if iriHost == ourHost { 463 blocked, err = f.db.IsBlocked(ctx, accountID, requestingAccount.ID) 464 if err != nil { 465 err = gtserror.Newf("db error checking block between other account and requester: %w", err) 466 return false, err 467 } 468 469 if blocked { 470 l.Trace("one or more otherIRIs belonging to us blocks requesting account") 471 err := newErrOtherIRIBlocked(requestingAccount.URI, false, otherIRIs) 472 return false, err 473 } 474 } 475 } 476 477 return false, nil 478 } 479 480 // FederatingCallbacks returns the application logic that handles 481 // ActivityStreams received from federating peers. 482 // 483 // Note that certain types of callbacks will be 'wrapped' with default 484 // behaviors supported natively by the library. Other callbacks 485 // compatible with streams.TypeResolver can be specified by 'other'. 486 // 487 // For example, setting the 'Create' field in the 488 // FederatingWrappedCallbacks lets an application dependency inject 489 // additional behaviors they want to take place, including the default 490 // behavior supplied by this library. This is guaranteed to be compliant 491 // with the ActivityPub Social protocol. 492 // 493 // To override the default behavior, instead supply the function in 494 // 'other', which does not guarantee the application will be compliant 495 // with the ActivityPub Social Protocol. 496 // 497 // Applications are not expected to handle every single ActivityStreams 498 // type and extension. The unhandled ones are passed to DefaultCallback. 499 func (f *federator) FederatingCallbacks(ctx context.Context) (wrapped pub.FederatingWrappedCallbacks, other []interface{}, err error) { 500 wrapped = pub.FederatingWrappedCallbacks{ 501 // OnFollow determines what action to take for this 502 // particular callback if a Follow Activity is handled. 503 // 504 // For our implementation, we always want to do nothing 505 // because we have internal logic for handling follows. 506 OnFollow: pub.OnFollowDoNothing, 507 } 508 509 // Override some default behaviors to trigger our own side effects. 510 other = []interface{}{ 511 func(ctx context.Context, undo vocab.ActivityStreamsUndo) error { 512 return f.FederatingDB().Undo(ctx, undo) 513 }, 514 func(ctx context.Context, accept vocab.ActivityStreamsAccept) error { 515 return f.FederatingDB().Accept(ctx, accept) 516 }, 517 func(ctx context.Context, reject vocab.ActivityStreamsReject) error { 518 return f.FederatingDB().Reject(ctx, reject) 519 }, 520 func(ctx context.Context, announce vocab.ActivityStreamsAnnounce) error { 521 return f.FederatingDB().Announce(ctx, announce) 522 }, 523 } 524 525 return 526 } 527 528 // DefaultCallback is called for types that go-fed can deserialize but 529 // are not handled by the application's callbacks returned in the 530 // Callbacks method. 531 // 532 // Applications are not expected to handle every single ActivityStreams 533 // type and extension, so the unhandled ones are passed to 534 // DefaultCallback. 535 func (f *federator) DefaultCallback(ctx context.Context, activity pub.Activity) error { 536 log.Debugf(ctx, "received unhandle-able activity type (%s) so ignoring it", activity.GetTypeName()) 537 return nil 538 } 539 540 // MaxInboxForwardingRecursionDepth determines how deep to search within 541 // an activity to determine if inbox forwarding needs to occur. 542 // 543 // Zero or negative numbers indicate infinite recursion. 544 func (f *federator) MaxInboxForwardingRecursionDepth(ctx context.Context) int { 545 // TODO 546 return 4 547 } 548 549 // MaxDeliveryRecursionDepth determines how deep to search within 550 // collections owned by peers when they are targeted to receive a 551 // delivery. 552 // 553 // Zero or negative numbers indicate infinite recursion. 554 func (f *federator) MaxDeliveryRecursionDepth(ctx context.Context) int { 555 // TODO 556 return 4 557 } 558 559 // FilterForwarding allows the implementation to apply business logic 560 // such as blocks, spam filtering, and so on to a list of potential 561 // Collections and OrderedCollections of recipients when inbox 562 // forwarding has been triggered. 563 // 564 // The activity is provided as a reference for more intelligent 565 // logic to be used, but the implementation must not modify it. 566 func (f *federator) FilterForwarding(ctx context.Context, potentialRecipients []*url.URL, a pub.Activity) ([]*url.URL, error) { 567 // TODO 568 return []*url.URL{}, nil 569 } 570 571 // GetInbox returns the OrderedCollection inbox of the actor for this 572 // context. It is up to the implementation to provide the correct 573 // collection for the kind of authorization given in the request. 574 // 575 // AuthenticateGetInbox will be called prior to this. 576 // 577 // Always called, regardless whether the Federated Protocol or Social 578 // API is enabled. 579 func (f *federator) GetInbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) { 580 // IMPLEMENTATION NOTE: For GoToSocial, we serve GETS to outboxes and inboxes through 581 // the CLIENT API, not through the federation API, so we just do nothing here. 582 return streams.NewActivityStreamsOrderedCollectionPage(), nil 583 }