util.go (11353B)
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 federatingdb 19 20 import ( 21 "context" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "net/url" 26 27 "codeberg.org/gruf/go-logger/v2/level" 28 "github.com/superseriousbusiness/activity/streams" 29 "github.com/superseriousbusiness/activity/streams/vocab" 30 "github.com/superseriousbusiness/gotosocial/internal/ap" 31 "github.com/superseriousbusiness/gotosocial/internal/config" 32 "github.com/superseriousbusiness/gotosocial/internal/db" 33 "github.com/superseriousbusiness/gotosocial/internal/gtscontext" 34 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 35 "github.com/superseriousbusiness/gotosocial/internal/id" 36 "github.com/superseriousbusiness/gotosocial/internal/log" 37 "github.com/superseriousbusiness/gotosocial/internal/uris" 38 ) 39 40 func sameActor(actor1 vocab.ActivityStreamsActorProperty, actor2 vocab.ActivityStreamsActorProperty) bool { 41 if actor1 == nil || actor2 == nil { 42 return false 43 } 44 45 for a1Iter := actor1.Begin(); a1Iter != actor1.End(); a1Iter = a1Iter.Next() { 46 for a2Iter := actor2.Begin(); a2Iter != actor2.End(); a2Iter = a2Iter.Next() { 47 if a1Iter.GetIRI() == nil { 48 return false 49 } 50 51 if a2Iter.GetIRI() == nil { 52 return false 53 } 54 55 if a1Iter.GetIRI().String() == a2Iter.GetIRI().String() { 56 return true 57 } 58 } 59 } 60 61 return false 62 } 63 64 // NewID creates a new IRI id for the provided activity or object. The 65 // implementation does not need to set the 'id' property and simply 66 // needs to determine the value. 67 // 68 // The go-fed library will handle setting the 'id' property on the 69 // activity or object provided with the value returned. 70 func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL, err error) { 71 if log.Level() >= level.DEBUG { 72 i, err := marshalItem(t) 73 if err != nil { 74 return nil, err 75 } 76 l := log.WithContext(ctx). 77 WithField("newID", i) 78 l.Debug("entering NewID") 79 } 80 81 switch t.GetTypeName() { 82 case ap.ActivityFollow: 83 // FOLLOW 84 // ID might already be set on a follow we've created, so check it here and return it if it is 85 follow, ok := t.(vocab.ActivityStreamsFollow) 86 if !ok { 87 return nil, errors.New("newid: follow couldn't be parsed into vocab.ActivityStreamsFollow") 88 } 89 idProp := follow.GetJSONLDId() 90 if idProp != nil { 91 if idProp.IsIRI() { 92 return idProp.GetIRI(), nil 93 } 94 } 95 // it's not set so create one based on the actor set on the follow (ie., the followER not the followEE) 96 actorProp := follow.GetActivityStreamsActor() 97 if actorProp != nil { 98 for iter := actorProp.Begin(); iter != actorProp.End(); iter = iter.Next() { 99 // take the IRI of the first actor we can find (there should only be one) 100 if iter.IsIRI() { 101 // if there's an error here, just use the fallback behavior -- we don't need to return an error here 102 if actorAccount, err := f.state.DB.GetAccountByURI(ctx, iter.GetIRI().String()); err == nil { 103 newID, err := id.NewRandomULID() 104 if err != nil { 105 return nil, err 106 } 107 return url.Parse(uris.GenerateURIForFollow(actorAccount.Username, newID)) 108 } 109 } 110 } 111 } 112 case ap.ObjectNote: 113 // NOTE aka STATUS 114 // ID might already be set on a note we've created, so check it here and return it if it is 115 note, ok := t.(vocab.ActivityStreamsNote) 116 if !ok { 117 return nil, errors.New("newid: note couldn't be parsed into vocab.ActivityStreamsNote") 118 } 119 idProp := note.GetJSONLDId() 120 if idProp != nil { 121 if idProp.IsIRI() { 122 return idProp.GetIRI(), nil 123 } 124 } 125 case ap.ActivityLike: 126 // LIKE aka FAVE 127 // ID might already be set on a fave we've created, so check it here and return it if it is 128 fave, ok := t.(vocab.ActivityStreamsLike) 129 if !ok { 130 return nil, errors.New("newid: fave couldn't be parsed into vocab.ActivityStreamsLike") 131 } 132 idProp := fave.GetJSONLDId() 133 if idProp != nil { 134 if idProp.IsIRI() { 135 return idProp.GetIRI(), nil 136 } 137 } 138 case ap.ActivityCreate: 139 // CREATE 140 // ID might already be set on a Create, so check it here and return it if it is 141 create, ok := t.(vocab.ActivityStreamsCreate) 142 if !ok { 143 return nil, errors.New("newid: create couldn't be parsed into vocab.ActivityStreamsCreate") 144 } 145 idProp := create.GetJSONLDId() 146 if idProp != nil { 147 if idProp.IsIRI() { 148 return idProp.GetIRI(), nil 149 } 150 } 151 case ap.ActivityAnnounce: 152 // ANNOUNCE aka BOOST 153 // ID might already be set on an announce we've created, so check it here and return it if it is 154 announce, ok := t.(vocab.ActivityStreamsAnnounce) 155 if !ok { 156 return nil, errors.New("newid: announce couldn't be parsed into vocab.ActivityStreamsAnnounce") 157 } 158 idProp := announce.GetJSONLDId() 159 if idProp != nil { 160 if idProp.IsIRI() { 161 return idProp.GetIRI(), nil 162 } 163 } 164 case ap.ActivityUpdate: 165 // UPDATE 166 // ID might already be set on an update we've created, so check it here and return it if it is 167 update, ok := t.(vocab.ActivityStreamsUpdate) 168 if !ok { 169 return nil, errors.New("newid: update couldn't be parsed into vocab.ActivityStreamsUpdate") 170 } 171 idProp := update.GetJSONLDId() 172 if idProp != nil { 173 if idProp.IsIRI() { 174 return idProp.GetIRI(), nil 175 } 176 } 177 case ap.ActivityBlock: 178 // BLOCK 179 // ID might already be set on a block we've created, so check it here and return it if it is 180 block, ok := t.(vocab.ActivityStreamsBlock) 181 if !ok { 182 return nil, errors.New("newid: block couldn't be parsed into vocab.ActivityStreamsBlock") 183 } 184 idProp := block.GetJSONLDId() 185 if idProp != nil { 186 if idProp.IsIRI() { 187 return idProp.GetIRI(), nil 188 } 189 } 190 case ap.ActivityUndo: 191 // UNDO 192 // ID might already be set on an undo we've created, so check it here and return it if it is 193 undo, ok := t.(vocab.ActivityStreamsUndo) 194 if !ok { 195 return nil, errors.New("newid: undo couldn't be parsed into vocab.ActivityStreamsUndo") 196 } 197 idProp := undo.GetJSONLDId() 198 if idProp != nil { 199 if idProp.IsIRI() { 200 return idProp.GetIRI(), nil 201 } 202 } 203 } 204 205 // fallback default behavior: just return a random ULID after our protocol and host 206 newID, err := id.NewRandomULID() 207 if err != nil { 208 return nil, err 209 } 210 211 return url.Parse(fmt.Sprintf("%s://%s/%s", config.GetProtocol(), config.GetHost(), newID)) 212 } 213 214 // ActorForOutbox fetches the actor's IRI for the given outbox IRI. 215 // 216 // The library makes this call only after acquiring a lock first. 217 func (f *federatingDB) ActorForOutbox(ctx context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error) { 218 acct, err := f.getAccountForIRI(ctx, outboxIRI) 219 if err != nil { 220 return nil, err 221 } 222 return url.Parse(acct.URI) 223 } 224 225 // ActorForInbox fetches the actor's IRI for the given outbox IRI. 226 // 227 // The library makes this call only after acquiring a lock first. 228 func (f *federatingDB) ActorForInbox(ctx context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error) { 229 acct, err := f.getAccountForIRI(ctx, inboxIRI) 230 if err != nil { 231 return nil, err 232 } 233 return url.Parse(acct.URI) 234 } 235 236 // getAccountForIRI returns the account that corresponds to or owns the given IRI. 237 func (f *federatingDB) getAccountForIRI(ctx context.Context, iri *url.URL) (*gtsmodel.Account, error) { 238 var ( 239 acct *gtsmodel.Account 240 err error 241 ) 242 243 switch { 244 case uris.IsUserPath(iri): 245 if acct, err = f.state.DB.GetAccountByURI(ctx, iri.String()); err != nil { 246 if err == db.ErrNoEntries { 247 return nil, fmt.Errorf("no actor found that corresponds to uri %s", iri.String()) 248 } 249 return nil, fmt.Errorf("db error searching for actor with uri %s", iri.String()) 250 } 251 return acct, nil 252 case uris.IsInboxPath(iri): 253 if acct, err = f.state.DB.GetAccountByInboxURI(ctx, iri.String()); err != nil { 254 if err == db.ErrNoEntries { 255 return nil, fmt.Errorf("no actor found that corresponds to inbox %s", iri.String()) 256 } 257 return nil, fmt.Errorf("db error searching for actor with inbox %s", iri.String()) 258 } 259 return acct, nil 260 case uris.IsOutboxPath(iri): 261 if acct, err = f.state.DB.GetAccountByOutboxURI(ctx, iri.String()); err != nil { 262 if err == db.ErrNoEntries { 263 return nil, fmt.Errorf("no actor found that corresponds to outbox %s", iri.String()) 264 } 265 return nil, fmt.Errorf("db error searching for actor with outbox %s", iri.String()) 266 } 267 return acct, nil 268 case uris.IsFollowersPath(iri): 269 if acct, err = f.state.DB.GetAccountByFollowersURI(ctx, iri.String()); err != nil { 270 if err == db.ErrNoEntries { 271 return nil, fmt.Errorf("no actor found that corresponds to followers_uri %s", iri.String()) 272 } 273 return nil, fmt.Errorf("db error searching for actor with followers_uri %s", iri.String()) 274 } 275 return acct, nil 276 case uris.IsFollowingPath(iri): 277 if acct, err = f.state.DB.GetAccountByFollowingURI(ctx, iri.String()); err != nil { 278 if err == db.ErrNoEntries { 279 return nil, fmt.Errorf("no actor found that corresponds to following_uri %s", iri.String()) 280 } 281 return nil, fmt.Errorf("db error searching for actor with following_uri %s", iri.String()) 282 } 283 return acct, nil 284 default: 285 return nil, fmt.Errorf("getActorForIRI: iri %s not recognised", iri) 286 } 287 } 288 289 // collectFollows takes a slice of iris and converts them into ActivityStreamsCollection of IRIs. 290 func (f *federatingDB) collectIRIs(ctx context.Context, iris []*url.URL) (vocab.ActivityStreamsCollection, error) { 291 collection := streams.NewActivityStreamsCollection() 292 items := streams.NewActivityStreamsItemsProperty() 293 for _, i := range iris { 294 items.AppendIRI(i) 295 } 296 collection.SetActivityStreamsItems(items) 297 return collection, nil 298 } 299 300 // extractFromCtx extracts some useful values from a context passed into the federatingDB: 301 // 302 // - The account that owns the inbox or URI being interacted with. 303 // - The account that POSTed a request to the inbox. 304 // - Whether this is an internal request (one originating not from 305 // the API but from inside the instance). 306 // 307 // If the request is internal, the caller can assume that the activity has 308 // already been processed elsewhere, and should return with no further action. 309 func extractFromCtx(ctx context.Context) (receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, internal bool) { 310 receivingAccount = gtscontext.ReceivingAccount(ctx) 311 requestingAccount = gtscontext.RequestingAccount(ctx) 312 313 // If the receiving account wasn't set on the context, that 314 // means this request didn't pass through the API, but 315 // came from inside GtS as the result of a local activity. 316 internal = receivingAccount == nil 317 318 return 319 } 320 321 func marshalItem(item vocab.Type) (string, error) { 322 m, err := ap.Serialize(item) 323 if err != nil { 324 return "", err 325 } 326 327 b, err := json.Marshal(m) 328 if err != nil { 329 return "", err 330 } 331 332 return string(b), nil 333 }