fromfederator.go (13207B)
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 processing 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "net/url" 25 26 "codeberg.org/gruf/go-kv" 27 "codeberg.org/gruf/go-logger/v2/level" 28 "github.com/superseriousbusiness/gotosocial/internal/ap" 29 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 30 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 31 "github.com/superseriousbusiness/gotosocial/internal/id" 32 "github.com/superseriousbusiness/gotosocial/internal/log" 33 "github.com/superseriousbusiness/gotosocial/internal/messages" 34 ) 35 36 // ProcessFromFederator reads the APActivityType and APObjectType of an incoming message from the federator, 37 // and directs the message into the appropriate side effect handler function, or simply does nothing if there's 38 // no handler function defined for the combination of Activity and Object. 39 func (p *Processor) ProcessFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { 40 // Allocate new log fields slice 41 fields := make([]kv.Field, 3, 5) 42 fields[0] = kv.Field{"activityType", federatorMsg.APActivityType} 43 fields[1] = kv.Field{"objectType", federatorMsg.APObjectType} 44 fields[2] = kv.Field{"toAccount", federatorMsg.ReceivingAccount.Username} 45 46 if federatorMsg.APIri != nil { 47 // An IRI was supplied, append to log 48 fields = append(fields, kv.Field{ 49 "iri", federatorMsg.APIri, 50 }) 51 } 52 53 if federatorMsg.GTSModel != nil && 54 log.Level() >= level.DEBUG { 55 // Append converted model to log 56 fields = append(fields, kv.Field{ 57 "model", federatorMsg.GTSModel, 58 }) 59 } 60 61 // Log this federated message 62 l := log.WithContext(ctx).WithFields(fields...) 63 l.Info("processing from federator") 64 65 switch federatorMsg.APActivityType { 66 case ap.ActivityCreate: 67 // CREATE SOMETHING 68 switch federatorMsg.APObjectType { 69 case ap.ObjectNote: 70 // CREATE A STATUS 71 return p.processCreateStatusFromFederator(ctx, federatorMsg) 72 case ap.ActivityLike: 73 // CREATE A FAVE 74 return p.processCreateFaveFromFederator(ctx, federatorMsg) 75 case ap.ActivityFollow: 76 // CREATE A FOLLOW REQUEST 77 return p.processCreateFollowRequestFromFederator(ctx, federatorMsg) 78 case ap.ActivityAnnounce: 79 // CREATE AN ANNOUNCE 80 return p.processCreateAnnounceFromFederator(ctx, federatorMsg) 81 case ap.ActivityBlock: 82 // CREATE A BLOCK 83 return p.processCreateBlockFromFederator(ctx, federatorMsg) 84 case ap.ActivityFlag: 85 // CREATE A FLAG / REPORT 86 return p.processCreateFlagFromFederator(ctx, federatorMsg) 87 } 88 case ap.ActivityUpdate: 89 // UPDATE SOMETHING 90 if federatorMsg.APObjectType == ap.ObjectProfile { 91 // UPDATE AN ACCOUNT 92 return p.processUpdateAccountFromFederator(ctx, federatorMsg) 93 } 94 case ap.ActivityDelete: 95 // DELETE SOMETHING 96 switch federatorMsg.APObjectType { 97 case ap.ObjectNote: 98 // DELETE A STATUS 99 return p.processDeleteStatusFromFederator(ctx, federatorMsg) 100 case ap.ObjectProfile: 101 // DELETE A PROFILE/ACCOUNT 102 return p.processDeleteAccountFromFederator(ctx, federatorMsg) 103 } 104 } 105 106 // not a combination we can/need to process 107 return nil 108 } 109 110 // processCreateStatusFromFederator handles Activity Create and Object Note 111 func (p *Processor) processCreateStatusFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { 112 // check for either an IRI that we still need to dereference, OR an already dereferenced 113 // and converted status pinned to the message. 114 var ( 115 status *gtsmodel.Status 116 err error 117 ) 118 119 if federatorMsg.GTSModel != nil { 120 var ok bool 121 122 // there's a gts model already pinned to the message, it should be a status 123 if status, ok = federatorMsg.GTSModel.(*gtsmodel.Status); !ok { 124 return gtserror.New("Note was not parseable as *gtsmodel.Status") 125 } 126 127 // Since this was a create originating AP object 128 // statusable may have been set on message (no problem if not). 129 statusable, _ := federatorMsg.APObjectModel.(ap.Statusable) 130 131 // Call refresh on status to deref if necessary etc. 132 status, _, err = p.federator.RefreshStatus(ctx, 133 federatorMsg.ReceivingAccount.Username, 134 status, 135 statusable, 136 false, 137 ) 138 if err != nil { 139 return err 140 } 141 } else { 142 // no model pinned, we need to dereference based on the IRI 143 if federatorMsg.APIri == nil { 144 return gtserror.New("status was not pinned to federatorMsg, and neither was an IRI for us to dereference") 145 } 146 147 status, _, err = p.federator.GetStatusByURI(ctx, federatorMsg.ReceivingAccount.Username, federatorMsg.APIri) 148 if err != nil { 149 return err 150 } 151 } 152 153 if status.Account == nil || status.Account.IsRemote() { 154 // Either no account attached yet, or a remote account. 155 // Both situations we need to parse account URI to fetch it. 156 remoteAccURI, err := url.Parse(status.AccountURI) 157 if err != nil { 158 return err 159 } 160 161 // Ensure that account for this status has been deref'd. 162 status.Account, _, err = p.federator.GetAccountByURI(ctx, 163 federatorMsg.ReceivingAccount.Username, 164 remoteAccURI, 165 ) 166 if err != nil { 167 return err 168 } 169 } 170 171 if status.InReplyToID != "" { 172 // Interaction counts changed on the replied status; 173 // uncache the prepared version from all timelines. 174 p.invalidateStatusFromTimelines(ctx, status.InReplyToID) 175 } 176 177 if err := p.timelineAndNotifyStatus(ctx, status); err != nil { 178 return gtserror.Newf("error timelining status: %w", err) 179 } 180 181 return nil 182 } 183 184 // processCreateFaveFromFederator handles Activity Create with Object Like. 185 func (p *Processor) processCreateFaveFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { 186 statusFave, ok := federatorMsg.GTSModel.(*gtsmodel.StatusFave) 187 if !ok { 188 return gtserror.New("Like was not parseable as *gtsmodel.StatusFave") 189 } 190 191 if err := p.notifyFave(ctx, statusFave); err != nil { 192 return gtserror.Newf("error notifying status fave: %w", err) 193 } 194 195 // Interaction counts changed on the faved status; 196 // uncache the prepared version from all timelines. 197 p.invalidateStatusFromTimelines(ctx, statusFave.StatusID) 198 199 return nil 200 } 201 202 // processCreateFollowRequestFromFederator handles Activity Create and Object Follow 203 func (p *Processor) processCreateFollowRequestFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { 204 followRequest, ok := federatorMsg.GTSModel.(*gtsmodel.FollowRequest) 205 if !ok { 206 return errors.New("incomingFollowRequest was not parseable as *gtsmodel.FollowRequest") 207 } 208 209 // make sure the account is pinned 210 if followRequest.Account == nil { 211 a, err := p.state.DB.GetAccountByID(ctx, followRequest.AccountID) 212 if err != nil { 213 return err 214 } 215 followRequest.Account = a 216 } 217 218 // Get the remote account to make sure the avi and header are cached. 219 if followRequest.Account.Domain != "" { 220 remoteAccountID, err := url.Parse(followRequest.Account.URI) 221 if err != nil { 222 return err 223 } 224 225 a, _, err := p.federator.GetAccountByURI(ctx, 226 federatorMsg.ReceivingAccount.Username, 227 remoteAccountID, 228 ) 229 if err != nil { 230 return err 231 } 232 233 followRequest.Account = a 234 } 235 236 if followRequest.TargetAccount == nil { 237 a, err := p.state.DB.GetAccountByID(ctx, followRequest.TargetAccountID) 238 if err != nil { 239 return err 240 } 241 followRequest.TargetAccount = a 242 } 243 244 if *followRequest.TargetAccount.Locked { 245 // if the account is locked just notify the follow request and nothing else 246 return p.notifyFollowRequest(ctx, followRequest) 247 } 248 249 // if the target account isn't locked, we should already accept the follow and notify about the new follower instead 250 follow, err := p.state.DB.AcceptFollowRequest(ctx, followRequest.AccountID, followRequest.TargetAccountID) 251 if err != nil { 252 return err 253 } 254 255 if err := p.federateAcceptFollowRequest(ctx, follow); err != nil { 256 return err 257 } 258 259 return p.notifyFollow(ctx, follow, followRequest.TargetAccount) 260 } 261 262 // processCreateAnnounceFromFederator handles Activity Create with Object Announce. 263 func (p *Processor) processCreateAnnounceFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { 264 status, ok := federatorMsg.GTSModel.(*gtsmodel.Status) 265 if !ok { 266 return gtserror.New("Announce was not parseable as *gtsmodel.Status") 267 } 268 269 // Dereference status that this status boosts. 270 if err := p.federator.DereferenceAnnounce(ctx, status, federatorMsg.ReceivingAccount.Username); err != nil { 271 return gtserror.Newf("error dereferencing announce: %w", err) 272 } 273 274 // Generate an ID for the boost wrapper status. 275 statusID, err := id.NewULIDFromTime(status.CreatedAt) 276 if err != nil { 277 return gtserror.Newf("error generating id: %w", err) 278 } 279 status.ID = statusID 280 281 // Store, timeline, and notify. 282 if err := p.state.DB.PutStatus(ctx, status); err != nil { 283 return gtserror.Newf("db error inserting status: %w", err) 284 } 285 286 if err := p.timelineAndNotifyStatus(ctx, status); err != nil { 287 return gtserror.Newf("error timelining status: %w", err) 288 } 289 290 if err := p.notifyAnnounce(ctx, status); err != nil { 291 return gtserror.Newf("error notifying status: %w", err) 292 } 293 294 // Interaction counts changed on the boosted status; 295 // uncache the prepared version from all timelines. 296 p.invalidateStatusFromTimelines(ctx, status.ID) 297 298 return nil 299 } 300 301 // processCreateBlockFromFederator handles Activity Create and Object Block 302 func (p *Processor) processCreateBlockFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { 303 block, ok := federatorMsg.GTSModel.(*gtsmodel.Block) 304 if !ok { 305 return errors.New("block was not parseable as *gtsmodel.Block") 306 } 307 308 // remove any of the blocking account's statuses from the blocked account's timeline, and vice versa 309 if err := p.state.Timelines.Home.WipeItemsFromAccountID(ctx, block.AccountID, block.TargetAccountID); err != nil { 310 return err 311 } 312 if err := p.state.Timelines.Home.WipeItemsFromAccountID(ctx, block.TargetAccountID, block.AccountID); err != nil { 313 return err 314 } 315 // TODO: same with notifications 316 // TODO: same with bookmarks 317 318 return nil 319 } 320 321 func (p *Processor) processCreateFlagFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { 322 incomingReport, ok := federatorMsg.GTSModel.(*gtsmodel.Report) 323 if !ok { 324 return errors.New("flag was not parseable as *gtsmodel.Report") 325 } 326 327 // TODO: handle additional side effects of flag creation: 328 // - notify admins by dm / notification 329 330 return p.emailReport(ctx, incomingReport) 331 } 332 333 // processUpdateAccountFromFederator handles Activity Update and Object Profile 334 func (p *Processor) processUpdateAccountFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { 335 incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account) 336 if !ok { 337 return errors.New("*gtsmodel.Account was not parseable on update account message") 338 } 339 340 // Because this was an Update, the new AP Object should be set on the message. 341 incomingAccountable, ok := federatorMsg.APObjectModel.(ap.Accountable) 342 if !ok { 343 return errors.New("Accountable was not parseable on update account message") 344 } 345 346 // Fetch up-to-date bio, avatar, header, etc. 347 _, _, err := p.federator.RefreshAccount( 348 ctx, 349 federatorMsg.ReceivingAccount.Username, 350 incomingAccount, 351 incomingAccountable, 352 true, 353 ) 354 if err != nil { 355 return fmt.Errorf("error enriching updated account from federator: %s", err) 356 } 357 358 return nil 359 } 360 361 // processDeleteStatusFromFederator handles Activity Delete and Object Note 362 func (p *Processor) processDeleteStatusFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { 363 status, ok := federatorMsg.GTSModel.(*gtsmodel.Status) 364 if !ok { 365 return errors.New("Note was not parseable as *gtsmodel.Status") 366 } 367 368 // Delete attachments from this status, since this request 369 // comes from the federating API, and there's no way the 370 // poster can do a delete + redraft for it on our instance. 371 deleteAttachments := true 372 if err := p.wipeStatus(ctx, status, deleteAttachments); err != nil { 373 return gtserror.Newf("error wiping status: %w", err) 374 } 375 376 if status.InReplyToID != "" { 377 // Interaction counts changed on the replied status; 378 // uncache the prepared version from all timelines. 379 p.invalidateStatusFromTimelines(ctx, status.InReplyToID) 380 } 381 382 return nil 383 } 384 385 // processDeleteAccountFromFederator handles Activity Delete and Object Profile 386 func (p *Processor) processDeleteAccountFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { 387 account, ok := federatorMsg.GTSModel.(*gtsmodel.Account) 388 if !ok { 389 return errors.New("account delete was not parseable as *gtsmodel.Account") 390 } 391 392 return p.account.Delete(ctx, account, account.ID) 393 }