gtsocial-umbx

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

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 }