gtsocial-umbx

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

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 }