base_actor.go (17232B)
1 package pub 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "net/url" 10 11 "github.com/superseriousbusiness/activity/streams" 12 "github.com/superseriousbusiness/activity/streams/vocab" 13 ) 14 15 // baseActor must satisfy the Actor interface. 16 var _ Actor = &baseActor{} 17 18 // baseActor is an application-independent ActivityPub implementation. It does 19 // not implement the entire protocol, and relies on a delegate to do so. It 20 // only implements the part of the protocol that is side-effect-free, allowing 21 // an existing application to write a DelegateActor that glues their application 22 // into the ActivityPub world. 23 // 24 // It is preferred to use a DelegateActor provided by this library, so that the 25 // application does not need to worry about the ActivityPub implementation. 26 type baseActor struct { 27 // delegate contains application-specific delegation logic. 28 delegate DelegateActor 29 // enableSocialProtocol enables or disables the Social API, the client to 30 // server part of ActivityPub. Useful if permitting remote clients to 31 // act on behalf of the users of the client application. 32 enableSocialProtocol bool 33 // enableFederatedProtocol enables or disables the Federated Protocol, or the 34 // server to server part of ActivityPub. Useful to permit integrating 35 // with the rest of the federative web. 36 enableFederatedProtocol bool 37 // clock simply tracks the current time. 38 clock Clock 39 } 40 41 // baseActorFederating must satisfy the FederatingActor interface. 42 var _ FederatingActor = &baseActorFederating{} 43 44 // baseActorFederating is a baseActor that also satisfies the FederatingActor 45 // interface. 46 // 47 // The baseActor is preserved as an Actor which will not successfully cast to a 48 // FederatingActor. 49 type baseActorFederating struct { 50 baseActor 51 } 52 53 // NewSocialActor builds a new Actor concept that handles only the Social 54 // Protocol part of ActivityPub. 55 // 56 // This Actor can be created once in an application and reused to handle 57 // multiple requests concurrently and for different endpoints. 58 // 59 // It leverages as much of go-fed as possible to ensure the implementation is 60 // compliant with the ActivityPub specification, while providing enough freedom 61 // to be productive without shooting one's self in the foot. 62 // 63 // Do not try to use NewSocialActor and NewFederatingActor together to cover 64 // both the Social and Federating parts of the protocol. Instead, use NewActor. 65 func NewSocialActor(c CommonBehavior, 66 c2s SocialProtocol, 67 db Database, 68 clock Clock) Actor { 69 return &baseActor{ 70 // Use SideEffectActor without s2s. 71 delegate: &SideEffectActor{ 72 common: c, 73 c2s: c2s, 74 db: db, 75 clock: clock, 76 }, 77 enableSocialProtocol: true, 78 clock: clock, 79 } 80 } 81 82 // NewFederatingActor builds a new Actor concept that handles only the Federating 83 // Protocol part of ActivityPub. 84 // 85 // This Actor can be created once in an application and reused to handle 86 // multiple requests concurrently and for different endpoints. 87 // 88 // It leverages as much of go-fed as possible to ensure the implementation is 89 // compliant with the ActivityPub specification, while providing enough freedom 90 // to be productive without shooting one's self in the foot. 91 // 92 // Do not try to use NewSocialActor and NewFederatingActor together to cover 93 // both the Social and Federating parts of the protocol. Instead, use NewActor. 94 func NewFederatingActor(c CommonBehavior, 95 s2s FederatingProtocol, 96 db Database, 97 clock Clock) FederatingActor { 98 return &baseActorFederating{ 99 baseActor{ 100 // Use SideEffectActor without c2s. 101 delegate: &SideEffectActor{ 102 common: c, 103 s2s: s2s, 104 db: db, 105 clock: clock, 106 }, 107 enableFederatedProtocol: true, 108 clock: clock, 109 }, 110 } 111 } 112 113 // NewActor builds a new Actor concept that handles both the Social and 114 // Federating Protocol parts of ActivityPub. 115 // 116 // This Actor can be created once in an application and reused to handle 117 // multiple requests concurrently and for different endpoints. 118 // 119 // It leverages as much of go-fed as possible to ensure the implementation is 120 // compliant with the ActivityPub specification, while providing enough freedom 121 // to be productive without shooting one's self in the foot. 122 func NewActor(c CommonBehavior, 123 c2s SocialProtocol, 124 s2s FederatingProtocol, 125 db Database, 126 clock Clock) FederatingActor { 127 return &baseActorFederating{ 128 baseActor{ 129 delegate: NewSideEffectActor(c, s2s, c2s, db, clock), 130 enableSocialProtocol: true, 131 enableFederatedProtocol: true, 132 clock: clock, 133 }, 134 } 135 } 136 137 // NewCustomActor allows clients to create a custom ActivityPub implementation 138 // for the Social Protocol, Federating Protocol, or both. 139 // 140 // It still uses the library as a high-level scaffold, which has the benefit of 141 // allowing applications to grow into a custom ActivityPub solution without 142 // having to refactor the code that passes HTTP requests into the Actor. 143 // 144 // It is possible to create a DelegateActor that is not ActivityPub compliant. 145 // Use with due care. 146 // 147 // If you find yourself passing a SideEffectActor in as the DelegateActor, 148 // consider using NewActor, NewFederatingActor, or NewSocialActor instead. 149 func NewCustomActor(delegate DelegateActor, 150 enableSocialProtocol, enableFederatedProtocol bool, 151 clock Clock) FederatingActor { 152 return &baseActorFederating{ 153 baseActor{ 154 delegate: delegate, 155 enableSocialProtocol: enableSocialProtocol, 156 enableFederatedProtocol: enableFederatedProtocol, 157 clock: clock, 158 }, 159 } 160 } 161 162 // PostInbox implements the generic algorithm for handling a POST request to an 163 // actor's inbox independent on an application. It relies on a delegate to 164 // implement application specific functionality. 165 // 166 // Only supports serving data with identifiers having the HTTPS scheme. 167 func (b *baseActor) PostInbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error) { 168 return b.PostInboxScheme(c, w, r, "https") 169 } 170 171 // PostInbox implements the generic algorithm for handling a POST request to an 172 // actor's inbox independent on an application. It relies on a delegate to 173 // implement application specific functionality. 174 // 175 // Specifying the "scheme" allows for retrieving ActivityStreams content with 176 // identifiers such as HTTP, HTTPS, or other protocol schemes. 177 func (b *baseActor) PostInboxScheme(c context.Context, w http.ResponseWriter, r *http.Request, scheme string) (bool, error) { 178 // Do nothing if it is not an ActivityPub POST request. 179 if !isActivityPubPost(r) { 180 return false, nil 181 } 182 // If the Federated Protocol is not enabled, then this endpoint is not 183 // enabled. 184 if !b.enableFederatedProtocol { 185 w.WriteHeader(http.StatusMethodNotAllowed) 186 return true, nil 187 } 188 // Check the peer request is authentic. 189 c, authenticated, err := b.delegate.AuthenticatePostInbox(c, w, r) 190 if err != nil { 191 return true, err 192 } else if !authenticated { 193 return true, nil 194 } 195 // Begin processing the request, but have not yet applied 196 // authorization (ex: blocks). Obtain the activity reject unknown 197 // activities. 198 raw, err := ioutil.ReadAll(r.Body) 199 if err != nil { 200 return true, err 201 } 202 var m map[string]interface{} 203 if err = json.Unmarshal(raw, &m); err != nil { 204 return true, err 205 } 206 asValue, err := streams.ToType(c, m) 207 if err != nil && !streams.IsUnmatchedErr(err) { 208 return true, err 209 } else if streams.IsUnmatchedErr(err) { 210 // Respond with bad request -- we do not understand the type. 211 w.WriteHeader(http.StatusBadRequest) 212 return true, nil 213 } 214 activity, ok := asValue.(Activity) 215 if !ok { 216 return true, fmt.Errorf("activity streams value is not an Activity: %T", asValue) 217 } 218 if activity.GetJSONLDId() == nil { 219 w.WriteHeader(http.StatusBadRequest) 220 return true, nil 221 } 222 // Allow server implementations to set context data with a hook. 223 c, err = b.delegate.PostInboxRequestBodyHook(c, r, activity) 224 if err != nil { 225 return true, err 226 } 227 // Check authorization of the activity. 228 authorized, err := b.delegate.AuthorizePostInbox(c, w, activity) 229 if err != nil { 230 return true, err 231 } else if !authorized { 232 return true, nil 233 } 234 // Post the activity to the actor's inbox and trigger side effects for 235 // that particular Activity type. It is up to the delegate to resolve 236 // the given map. 237 inboxId := requestId(r, scheme) 238 err = b.delegate.PostInbox(c, inboxId, activity) 239 if err != nil { 240 // Special case: We know it is a bad request if the object or 241 // target properties needed to be populated, but weren't. 242 // 243 // Send the rejection to the peer. 244 if err == ErrObjectRequired || err == ErrTargetRequired { 245 w.WriteHeader(http.StatusBadRequest) 246 return true, nil 247 } 248 return true, err 249 } 250 // Our side effects are complete, now delegate determining whether to 251 // do inbox forwarding, as well as the action to do it. 252 if err := b.delegate.InboxForwarding(c, inboxId, activity); err != nil { 253 return true, err 254 } 255 // Request has been processed. Begin responding to the request. 256 // 257 // Simply respond with an OK status to the peer. 258 w.WriteHeader(http.StatusOK) 259 return true, nil 260 } 261 262 // GetInbox implements the generic algorithm for handling a GET request to an 263 // actor's inbox independent on an application. It relies on a delegate to 264 // implement application specific functionality. 265 func (b *baseActor) GetInbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error) { 266 // Do nothing if it is not an ActivityPub GET request. 267 if !isActivityPubGet(r) { 268 return false, nil 269 } 270 // Delegate authenticating and authorizing the request. 271 c, authenticated, err := b.delegate.AuthenticateGetInbox(c, w, r) 272 if err != nil { 273 return true, err 274 } else if !authenticated { 275 return true, nil 276 } 277 // Everything is good to begin processing the request. 278 oc, err := b.delegate.GetInbox(c, r) 279 if err != nil { 280 return true, err 281 } 282 // Deduplicate the 'orderedItems' property by ID. 283 err = dedupeOrderedItems(oc) 284 if err != nil { 285 return true, err 286 } 287 // Request has been processed. Begin responding to the request. 288 // 289 // Serialize the OrderedCollection. 290 m, err := streams.Serialize(oc) 291 if err != nil { 292 return true, err 293 } 294 raw, err := json.Marshal(m) 295 if err != nil { 296 return true, err 297 } 298 // Write the response. 299 addResponseHeaders(w.Header(), b.clock, raw) 300 w.WriteHeader(http.StatusOK) 301 n, err := w.Write(raw) 302 if err != nil { 303 return true, err 304 } else if n != len(raw) { 305 return true, fmt.Errorf("ResponseWriter.Write wrote %d of %d bytes", n, len(raw)) 306 } 307 return true, nil 308 } 309 310 // PostOutbox implements the generic algorithm for handling a POST request to an 311 // actor's outbox independent on an application. It relies on a delegate to 312 // implement application specific functionality. 313 // 314 // Only supports serving data with identifiers having the HTTPS scheme. 315 func (b *baseActor) PostOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error) { 316 return b.PostOutboxScheme(c, w, r, "https") 317 } 318 319 // PostOutbox implements the generic algorithm for handling a POST request to an 320 // actor's outbox independent on an application. It relies on a delegate to 321 // implement application specific functionality. 322 // 323 // Specifying the "scheme" allows for retrieving ActivityStreams content with 324 // identifiers such as HTTP, HTTPS, or other protocol schemes. 325 func (b *baseActor) PostOutboxScheme(c context.Context, w http.ResponseWriter, r *http.Request, scheme string) (bool, error) { 326 // Do nothing if it is not an ActivityPub POST request. 327 if !isActivityPubPost(r) { 328 return false, nil 329 } 330 // If the Social API is not enabled, then this endpoint is not enabled. 331 if !b.enableSocialProtocol { 332 w.WriteHeader(http.StatusMethodNotAllowed) 333 return true, nil 334 } 335 // Delegate authenticating and authorizing the request. 336 c, authenticated, err := b.delegate.AuthenticatePostOutbox(c, w, r) 337 if err != nil { 338 return true, err 339 } else if !authenticated { 340 return true, nil 341 } 342 // Everything is good to begin processing the request. 343 raw, err := ioutil.ReadAll(r.Body) 344 if err != nil { 345 return true, err 346 } 347 var m map[string]interface{} 348 if err = json.Unmarshal(raw, &m); err != nil { 349 return true, err 350 } 351 // Note that converting to a Type will NOT successfully convert types 352 // not known to go-fed. This prevents accidentally wrapping an Activity 353 // type unknown to go-fed in a Create below. Instead, 354 // streams.ErrUnhandledType will be returned here. 355 asValue, err := streams.ToType(c, m) 356 if err != nil && !streams.IsUnmatchedErr(err) { 357 return true, err 358 } else if streams.IsUnmatchedErr(err) { 359 // Respond with bad request -- we do not understand the type. 360 w.WriteHeader(http.StatusBadRequest) 361 return true, nil 362 } 363 // Allow server implementations to set context data with a hook. 364 c, err = b.delegate.PostOutboxRequestBodyHook(c, r, asValue) 365 if err != nil { 366 return true, err 367 } 368 // The HTTP request steps are complete, complete the rest of the outbox 369 // and delivery process. 370 outboxId := requestId(r, scheme) 371 activity, err := b.deliver(c, outboxId, asValue, m) 372 // Special case: We know it is a bad request if the object or 373 // target properties needed to be populated, but weren't. 374 // 375 // Send the rejection to the client. 376 if err == ErrObjectRequired || err == ErrTargetRequired { 377 w.WriteHeader(http.StatusBadRequest) 378 return true, nil 379 } else if err != nil { 380 return true, err 381 } 382 // Respond to the request with the new Activity's IRI location. 383 w.Header().Set(locationHeader, activity.GetJSONLDId().Get().String()) 384 w.WriteHeader(http.StatusCreated) 385 return true, nil 386 } 387 388 // GetOutbox implements the generic algorithm for handling a Get request to an 389 // actor's outbox independent on an application. It relies on a delegate to 390 // implement application specific functionality. 391 func (b *baseActor) GetOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error) { 392 // Do nothing if it is not an ActivityPub GET request. 393 if !isActivityPubGet(r) { 394 return false, nil 395 } 396 // Delegate authenticating and authorizing the request. 397 c, authenticated, err := b.delegate.AuthenticateGetOutbox(c, w, r) 398 if err != nil { 399 return true, err 400 } else if !authenticated { 401 return true, nil 402 } 403 // Everything is good to begin processing the request. 404 oc, err := b.delegate.GetOutbox(c, r) 405 if err != nil { 406 return true, err 407 } 408 // Request has been processed. Begin responding to the request. 409 // 410 // Serialize the OrderedCollection. 411 m, err := streams.Serialize(oc) 412 if err != nil { 413 return true, err 414 } 415 raw, err := json.Marshal(m) 416 if err != nil { 417 return true, err 418 } 419 // Write the response. 420 addResponseHeaders(w.Header(), b.clock, raw) 421 w.WriteHeader(http.StatusOK) 422 n, err := w.Write(raw) 423 if err != nil { 424 return true, err 425 } else if n != len(raw) { 426 return true, fmt.Errorf("ResponseWriter.Write wrote %d of %d bytes", n, len(raw)) 427 } 428 return true, nil 429 } 430 431 // deliver delegates all outbox handling steps and optionally will federate the 432 // activity if the federated protocol is enabled. 433 // 434 // This function is not exported so an Actor that only supports C2S cannot be 435 // type casted to a FederatingActor. It doesn't exactly fit the Send method 436 // signature anyways. 437 // 438 // Note: 'm' is nilable. 439 func (b *baseActor) deliver(c context.Context, outbox *url.URL, asValue vocab.Type, m map[string]interface{}) (activity Activity, err error) { 440 // If the value is not an Activity or type extending from Activity, then 441 // we need to wrap it in a Create Activity. 442 if !streams.IsOrExtendsActivityStreamsActivity(asValue) { 443 asValue, err = b.delegate.WrapInCreate(c, asValue, outbox) 444 if err != nil { 445 return 446 } 447 } 448 // At this point, this should be a safe conversion. If this error is 449 // triggered, then there is either a bug in the delegation of 450 // WrapInCreate, behavior is not lining up in the generated ExtendedBy 451 // code, or something else is incorrect with the type system. 452 var ok bool 453 activity, ok = asValue.(Activity) 454 if !ok { 455 err = fmt.Errorf("activity streams value is not an Activity: %T", asValue) 456 return 457 } 458 // Delegate generating new IDs for the activity and all new objects. 459 if err = b.delegate.AddNewIDs(c, activity); err != nil { 460 return 461 } 462 // Post the activity to the actor's outbox and trigger side effects for 463 // that particular Activity type. 464 // 465 // Since 'm' is nil-able and side effects may need access to literal nil 466 // values, such as for Update activities, ensure 'm' is non-nil. 467 if m == nil { 468 m, err = asValue.Serialize() 469 if err != nil { 470 return 471 } 472 } 473 deliverable, err := b.delegate.PostOutbox(c, activity, outbox, m) 474 if err != nil { 475 return 476 } 477 // Request has been processed and all side effects internal to this 478 // application server have finished. Begin side effects affecting other 479 // servers and/or the client who sent this request. 480 // 481 // If we are federating and the type is a deliverable one, then deliver 482 // the activity to federating peers. 483 if b.enableFederatedProtocol && deliverable { 484 if err = b.delegate.Deliver(c, outbox, activity); err != nil { 485 return 486 } 487 } 488 return 489 } 490 491 // Send is programmatically accessible if the federated protocol is enabled. 492 func (b *baseActorFederating) Send(c context.Context, outbox *url.URL, t vocab.Type) (Activity, error) { 493 return b.deliver(c, outbox, t, nil) 494 }