diff --git a/pub/actor.go b/pub/actor.go index 8c84eed..13e2396 100644 --- a/pub/actor.go +++ b/pub/actor.go @@ -13,6 +13,10 @@ import ( // Protocol), client-to-server (Social API), or both. The Actor represents the // server in either use case. // +// An actor can be created by calling NewSocialActor (only the Social Protocol +// is supported), NewFederatingActor (only the Federating Protocol is +// supported), NewActor (both are supported), or NewCustomActor (neither are). +// // Not all Actors have the same behaviors depending on the constructor used to // create them. Refer to the constructor's documentation to determine the exact // behavior of the Actor on an application. diff --git a/pub/base_actor.go b/pub/base_actor.go index 63a7e9c..f41bc62 100644 --- a/pub/base_actor.go +++ b/pub/base_actor.go @@ -124,11 +124,11 @@ func NewActor(c CommonBehavior, // for the Social Protocol, Federating Protocol, or both. // // It still uses the library as a high-level scaffold, which has the benefit of -// allowing applications to grow into a custom solution without having to -// refactor the code that passes HTTP requests into the Actor. +// allowing applications to grow into a custom ActivityPub solution without +// having to refactor the code that passes HTTP requests into the Actor. // // It is possible to create a DelegateActor that is not ActivityPub compliant. -// Use with care. +// Use with due care. func NewCustomActor(delegate DelegateActor, enableSocialProtocol, enableFederatedProtocol bool, clock Clock) Actor { diff --git a/pub/common_behavior.go b/pub/common_behavior.go index 6fbdd40..a826162 100644 --- a/pub/common_behavior.go +++ b/pub/common_behavior.go @@ -7,6 +7,9 @@ import ( // Common contains functions required for both the Social API and Federating // Protocol. +// +// It is passed to the library as a dependency injection from the client +// application. type CommonBehavior interface { // AuthenticateGetInbox delegates the authentication of a GET to an // inbox. diff --git a/pub/delegate_actor.go b/pub/delegate_actor.go index 6b7f07c..0bf0cf3 100644 --- a/pub/delegate_actor.go +++ b/pub/delegate_actor.go @@ -10,14 +10,17 @@ import ( // DelegateActor contains the detailed interface an application must satisfy in // order to implement the ActivityPub specification. // +// Note that an implementation of this interface is implicitly provided in the +// calls to NewActor, NewSocialActor, and NewFederatingActor. +// // Implementing the DelegateActor requires familiarity with the ActivityPub -// specification, it does not a strong enough abstraction for the client +// specification because it does not a strong enough abstraction for the client // application to ignore the ActivityPub spec. It is very possible to implement // this interface and build a foot-gun that trashes the fediverse without being // ActivityPub compliant. Please use with due consideration. // // Alternatively, build an application that uses the parts of the pub library -// that does not require implementing a DelegateActor so that the ActivityPub +// that do not require implementing a DelegateActor so that the ActivityPub // implementation is completely provided out of the box. type DelegateActor interface { // AuthenticatePostInbox delegates the authentication of a POST to an @@ -144,8 +147,8 @@ type DelegateActor interface { // // If an error is returned, it is returned to the caller of PostOutbox. Deliver(c context.Context, outbox *url.URL, activity Activity) error - // AuthenticatePostOutbox delegates the authentication of a POST to an - // outbox. + // AuthenticatePostOutbox delegates the authentication and authorization + // of a POST to an outbox. // // Only called if the Social API is enabled. // diff --git a/pub/federating_protocol.go b/pub/federating_protocol.go index 4686bc0..ff6e8f2 100644 --- a/pub/federating_protocol.go +++ b/pub/federating_protocol.go @@ -12,6 +12,9 @@ import ( // // It is only required if the client application wants to support the server-to- // server, or federating, protocol. +// +// It is passed to the library as a dependency injection from the client +// application. type FederatingProtocol interface { // AuthenticatePostInbox delegates the authentication of a POST to an // inbox. @@ -46,12 +49,21 @@ type FederatingProtocol interface { // to be processed. Blocked(c context.Context, actorIRIs []*url.URL) (blocked bool, err error) // Callbacks returns the application logic that handles ActivityStreams - // received from federating peers. Note that certain types of callbacks - // will be 'wrapped' with default behaviors supported natively by the - // library. Other callbacks compatible with streams.TypeResolver can - // be specified by 'other'. + // received from federating peers. // - // Note that the functions in 'wrapped' cannot be provided in 'other'. + // Note that certain types of callbacks will be 'wrapped' with default + // behaviors supported natively by the library. Other callbacks + // compatible with streams.TypeResolver can be specified by 'other'. + // + // For example, setting the 'Create' field in the + // FederatingWrappedCallbacks lets an application dependency inject + // additional behaviors they want to take place, including the default + // behavior supplied by this library. This is guaranteed to be compliant + // with the ActivityPub Social protocol. + // + // To override the default behavior, instead supply the function in + // 'other', which does not guarantee the application will be compliant + // with the ActivityPub Social Protocol. Callbacks(c context.Context) (wrapped FederatingWrappedCallbacks, other []interface{}) // MaxInboxForwardingRecursionDepth determines how deep to search within // an activity to determine if inbox forwarding needs to occur. diff --git a/pub/federating_wrapped_callbacks.go b/pub/federating_wrapped_callbacks.go index 9d3b050..8045b82 100644 --- a/pub/federating_wrapped_callbacks.go +++ b/pub/federating_wrapped_callbacks.go @@ -135,63 +135,91 @@ type FederatingWrappedCallbacks struct { newTransport func(c context.Context, actorBoxIRI *url.URL, gofedAgent string) (t Transport, err error) } -// disjoint ensures that the functions given do not share a type signature with -// the functions being wrapped in FederatingWrappedCallbacks. -func (w FederatingWrappedCallbacks) disjoint(fns []interface{}) error { - // TODO: Instead, if provided in "other" it should override this behavior. - var s string +// callbacks returns the WrappedCallbacks members into a single interface slice +// for use in streams.Resolver callbacks. +// +// If the given functions have a type that collides with the default behavior, +// then disable our default behavior +func (w FederatingWrappedCallbacks) callbacks(fns []interface{}) []interface{} { + enableCreate := true + enableUpdate := true + enableDelete := true + enableFollow := true + enableAccept := true + enableReject := true + enableAdd := true + enableRemove := true + enableLike := true + enableAnnounce := true + enableUndo := true + enableBlock := true for _, fn := range fns { switch fn.(type) { default: - // OK, no collision continue case func(context.Context, vocab.ActivityStreamsCreate) error: - s = "Create" + enableCreate = false case func(context.Context, vocab.ActivityStreamsUpdate) error: - s = "Update" + enableUpdate = false case func(context.Context, vocab.ActivityStreamsDelete) error: - s = "Delete" + enableDelete = false case func(context.Context, vocab.ActivityStreamsFollow) error: - s = "Follow" + enableFollow = false case func(context.Context, vocab.ActivityStreamsAccept) error: - s = "Accept" + enableAccept = false case func(context.Context, vocab.ActivityStreamsReject) error: - s = "Reject" + enableReject = false case func(context.Context, vocab.ActivityStreamsAdd) error: - s = "Add" + enableAdd = false case func(context.Context, vocab.ActivityStreamsRemove) error: - s = "Remove" + enableRemove = false case func(context.Context, vocab.ActivityStreamsLike) error: - s = "Like" + enableLike = false case func(context.Context, vocab.ActivityStreamsAnnounce) error: - s = "Announce" + enableAnnounce = false case func(context.Context, vocab.ActivityStreamsUndo) error: - s = "Undo" + enableUndo = false case func(context.Context, vocab.ActivityStreamsBlock) error: - s = "Block" + enableBlock = false } - return fmt.Errorf("callback function handling type %q conflicts with FederatingWrappedCallbacks", s) } - return nil -} - -// callbacks returns the WrappedCallbacks members into a single interface slice -// for use in streams.Resolver callbacks. -func (w FederatingWrappedCallbacks) callbacks() []interface{} { - return []interface{}{ - w.create, - w.update, - w.deleteFn, - w.follow, - w.accept, - w.reject, - w.add, - w.remove, - w.like, - w.announce, - w.undo, - w.block, + if enableCreate { + fns = append(fns, w.create) } + if enableUpdate { + fns = append(fns, w.update) + } + if enableDelete { + fns = append(fns, w.deleteFn) + } + if enableFollow { + fns = append(fns, w.follow) + } + if enableAccept { + fns = append(fns, w.accept) + } + if enableReject { + fns = append(fns, w.reject) + } + if enableAdd { + fns = append(fns, w.add) + } + if enableRemove { + fns = append(fns, w.remove) + } + if enableLike { + fns = append(fns, w.like) + } + if enableAnnounce { + fns = append(fns, w.announce) + } + if enableUndo { + fns = append(fns, w.undo) + } + if enableBlock { + fns = append(fns, w.block) + } + return fns } // create implements the federating Create activity side effects. diff --git a/pub/property_interfaces.go b/pub/property_interfaces.go index 343bf9e..b401344 100644 --- a/pub/property_interfaces.go +++ b/pub/property_interfaces.go @@ -5,12 +5,12 @@ import ( "net/url" ) -// inReplyToer is an ActivityStreams type with a 'inReplyTo' property +// inReplyToer is an ActivityStreams type with an 'inReplyTo' property type inReplyToer interface { GetActivityStreamsInReplyTo() vocab.ActivityStreamsInReplyToProperty } -// objecter is an ActivityStreams type with a 'object' property +// objecter is an ActivityStreams type with an 'object' property type objecter interface { GetActivityStreamsObject() vocab.ActivityStreamsObjectProperty } @@ -30,13 +30,13 @@ type hrefer interface { GetActivityStreamsHref() vocab.ActivityStreamsHrefProperty } -// itemser is an ActivityStreams type with a 'items' property +// itemser is an ActivityStreams type with an 'items' property type itemser interface { GetActivityStreamsItems() vocab.ActivityStreamsItemsProperty SetActivityStreamsItems(vocab.ActivityStreamsItemsProperty) } -// orderedItemser is an ActivityStreams type with a 'orderedItems' property +// orderedItemser is an ActivityStreams type with an 'orderedItems' property type orderedItemser interface { GetActivityStreamsOrderedItems() vocab.ActivityStreamsOrderedItemsProperty SetActivityStreamsOrderedItems(vocab.ActivityStreamsOrderedItemsProperty) @@ -47,7 +47,7 @@ type publisheder interface { GetActivityStreamsPublished() vocab.ActivityStreamsPublishedProperty } -// updateder is an ActivityStreams type with a 'updateder' property +// updateder is an ActivityStreams type with an 'updateder' property type updateder interface { GetActivityStreamsUpdated() vocab.ActivityStreamsUpdatedProperty } @@ -76,18 +76,18 @@ type bccer interface { SetActivityStreamsBcc(i vocab.ActivityStreamsBccProperty) } -// audiencer is an ActivityStreams type with a 'audience' property +// audiencer is an ActivityStreams type with an 'audience' property type audiencer interface { GetActivityStreamsAudience() vocab.ActivityStreamsAudienceProperty SetActivityStreamsAudience(i vocab.ActivityStreamsAudienceProperty) } -// inboxer is an ActivityStreams type with a 'inbox' property +// inboxer is an ActivityStreams type with an 'inbox' property type inboxer interface { GetActivityStreamsInbox() vocab.ActivityStreamsInboxProperty } -// attributedToer is an ActivityStreams type with a 'attributedTo' property +// attributedToer is an ActivityStreams type with an 'attributedTo' property type attributedToer interface { GetActivityStreamsAttributedTo() vocab.ActivityStreamsAttributedToProperty SetActivityStreamsAttributedTo(i vocab.ActivityStreamsAttributedToProperty) @@ -105,7 +105,7 @@ type shareser interface { SetActivityStreamsShares(i vocab.ActivityStreamsSharesProperty) } -// actorer is an ActivityStreams type with a 'actor' property +// actorer is an ActivityStreams type with an 'actor' property type actorer interface { GetActivityStreamsActor() vocab.ActivityStreamsActorProperty SetActivityStreamsActor(i vocab.ActivityStreamsActorProperty) diff --git a/pub/side_effect_actor.go b/pub/side_effect_actor.go index b22523e..2b46412 100644 --- a/pub/side_effect_actor.go +++ b/pub/side_effect_actor.go @@ -102,10 +102,7 @@ func (a *sideEffectActor) PostInbox(c context.Context, inboxIRI *url.URL, activi wrapped.db = a.db wrapped.inboxIRI = inboxIRI wrapped.newTransport = a.s2s.NewTransport - if err = wrapped.disjoint(other); err != nil { - return err - } - res, err := streams.NewTypeResolver(append(wrapped.callbacks(), other...)) + res, err := streams.NewTypeResolver(wrapped.callbacks(other)) if err != nil { return err } @@ -283,10 +280,7 @@ func (a *sideEffectActor) PostOutbox(c context.Context, activity Activity, outbo wrapped.rawActivity = rawJSON wrapped.clock = a.clock wrapped.deliverable = &deliverable - if e = wrapped.disjoint(other); e != nil { - return - } - res, err := streams.NewTypeResolver(append(wrapped.callbacks(), other...)) + res, err := streams.NewTypeResolver(wrapped.callbacks(other)) if err != nil { return } diff --git a/pub/social_protocol.go b/pub/social_protocol.go index c0a958c..ae6c94e 100644 --- a/pub/social_protocol.go +++ b/pub/social_protocol.go @@ -11,6 +11,9 @@ import ( // // It is only required if the client application wants to support the client-to- // server, or social, protocol. +// +// It is passed to the library as a dependency injection from the client +// application. type SocialProtocol interface { // AuthenticatePostOutbox delegates the authentication of a POST to an // outbox. @@ -32,12 +35,21 @@ type SocialProtocol interface { // to be processed. AuthenticatePostOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (shouldReturn bool, err error) // Callbacks returns the application logic that handles ActivityStreams - // received from C2S clients. Note that certain types of callbacks - // will be 'wrapped' with default behaviors supported natively by the - // library. Other callbacks compatible with streams.TypeResolver can - // be specified by 'other'. + // received from C2S clients. // - // Note that the functions in 'wrapped' cannot be provided in 'other'. + // Note that certain types of callbacks will be 'wrapped' with default + // behaviors supported natively by the library. Other callbacks + // compatible with streams.TypeResolver can be specified by 'other'. + // + // For example, setting the 'Create' field in the SocialWrappedCallbacks + // lets an application dependency inject additional behaviors they want + // to take place, including the default behavior supplied by this + // library. This is guaranteed to be compliant with the ActivityPub + // Social protocol. + // + // To override the default behavior, instead supply the function in + // 'other', which does not guarantee the application will be compliant + // with the ActivityPub Social Protocol. Callbacks(c context.Context) (wrapped SocialWrappedCallbacks, other []interface{}) // GetOutbox returns the OrderedCollection inbox of the actor for this // context. It is up to the implementation to provide the correct diff --git a/pub/social_wrapped_callbacks.go b/pub/social_wrapped_callbacks.go index 642afb5..571d79c 100644 --- a/pub/social_wrapped_callbacks.go +++ b/pub/social_wrapped_callbacks.go @@ -42,22 +42,35 @@ type SocialWrappedCallbacks struct { // Add handles additional side effects for the Add ActivityStreams // type. // - // TODO: Describe + // + // The wrapping function will add the 'object' IRIs to a specific + // 'target' collection if the 'target' collection(s) live on this + // server. Add func(context.Context, vocab.ActivityStreamsAdd) error // Remove handles additional side effects for the Remove ActivityStreams // type. // - // TODO: Describe + // The wrapping function will remove all 'object' IRIs from a specific + // 'target' collection if the 'target' collection(s) live on this + // server. Remove func(context.Context, vocab.ActivityStreamsRemove) error // Like handles additional side effects for the Like ActivityStreams // type. // - // TODO: Describe + // The wrapping function will add the objects on the activity to the + // "liked" collection of this actor. Like func(context.Context, vocab.ActivityStreamsLike) error // Undo handles additional side effects for the Undo ActivityStreams // type. // - // TODO: Describe + // + // The wrapping function ensures the 'actor' on the 'Undo' + // is be the same as the 'actor' on all Activities being undone. + // It enforces that the actors on the Undo must correspond to all of the + // 'object' actors in some manner. + // + // It is expected that the application will implement the proper + // reversal of activities that are being undone. Undo func(context.Context, vocab.ActivityStreamsUndo) error // Block handles additional side effects for the Block ActivityStreams // type. @@ -89,53 +102,73 @@ type SocialWrappedCallbacks struct { deliverable *bool } -// disjoint ensures that the functions given do not share a type signature with -// the functions being wrapped in SocialWrappedCallbacks. -func (w SocialWrappedCallbacks) disjoint(fns []interface{}) error { - var s string +// callbacks returns the WrappedCallbacks members into a single interface slice +// for use in streams.Resolver callbacks. +// +// If the given functions have a type that collides with the default behavior, +// then disable our default behavior +func (w SocialWrappedCallbacks) callbacks(fns []interface{}) []interface{} { + enableCreate := true + enableUpdate := true + enableDelete := true + enableFollow := true + enableAdd := true + enableRemove := true + enableLike := true + enableUndo := true + enableBlock := true for _, fn := range fns { switch fn.(type) { default: - // OK, no collision continue case func(context.Context, vocab.ActivityStreamsCreate) error: - s = "Create" + enableCreate = false case func(context.Context, vocab.ActivityStreamsUpdate) error: - s = "Update" + enableUpdate = false case func(context.Context, vocab.ActivityStreamsDelete) error: - s = "Delete" + enableDelete = false case func(context.Context, vocab.ActivityStreamsFollow) error: - s = "Follow" + enableFollow = false case func(context.Context, vocab.ActivityStreamsAdd) error: - s = "Add" + enableAdd = false case func(context.Context, vocab.ActivityStreamsRemove) error: - s = "Remove" + enableRemove = false case func(context.Context, vocab.ActivityStreamsLike) error: - s = "Like" + enableLike = false case func(context.Context, vocab.ActivityStreamsUndo) error: - s = "Undo" + enableUndo = false case func(context.Context, vocab.ActivityStreamsBlock) error: - s = "Block" + enableBlock = false } - return fmt.Errorf("callback function handling type %q conflicts with SocialWrappedCallbacks", s) } - return nil -} - -// callbacks returns the WrappedCallbacks members into a single interface slice -// for use in streams.Resolver callbacks. -func (w SocialWrappedCallbacks) callbacks() []interface{} { - return []interface{}{ - w.create, - w.update, - w.deleteFn, - w.follow, - w.add, - w.remove, - w.like, - w.undo, - w.block, + if enableCreate { + fns = append(fns, w.create) } + if enableUpdate { + fns = append(fns, w.update) + } + if enableDelete { + fns = append(fns, w.deleteFn) + } + if enableFollow { + fns = append(fns, w.follow) + } + if enableAdd { + fns = append(fns, w.add) + } + if enableRemove { + fns = append(fns, w.remove) + } + if enableLike { + fns = append(fns, w.like) + } + if enableUndo { + fns = append(fns, w.undo) + } + if enableBlock { + fns = append(fns, w.block) + } + return fns } // create implements the social Create activity side effects. diff --git a/pub/transport.go b/pub/transport.go index 0eace82..fa4c1b5 100644 --- a/pub/transport.go +++ b/pub/transport.go @@ -19,8 +19,15 @@ const ( acceptHeaderValue = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" ) -// Transport makes ActivityStreams calls to other servers in order to POST or -// GET ActivityStreams data. +// Transport makes ActivityStreams calls to other servers in order to send or +// receive ActivityStreams data. +// +// It is responsible for setting the appropriate request headers, signing the +// requests if needed, and facilitating the traffic between this server and +// another. +// +// The transport is exclusively used to issue requests on behalf of an actor, +// and is never sending requests on behalf of the server in general. // // It may be reused multiple times, but never concurrently. type Transport interface { @@ -52,10 +59,24 @@ type HttpSigTransport struct { privKey crypto.PrivateKey } -// NewHttpSigTransport returns a new HttpSigTransport. +// NewHttpSigTransport returns a new Transport. +// +// It sends requests specifically on behalf of a specific actor on this server. +// The actor's credentials are used to add an HTTP Signature to requests, which +// requires an actor's private key, a unique identifier for their public key, +// and an HTTP Signature signing algorithm. +// +// The client lets users issue requests through any HTTP client, including the +// standard library's HTTP client. +// +// The appAgent uniquely identifies the calling application's requests, so peers +// may aid debugging the requests incoming from this server. Note that the +// agent string will also include one for go-fed, so at minimum peer servers can +// reach out to the go-fed library to aid in notifying implementors of malformed +// or unsupported requests. func NewHttpSigTransport( client HttpClient, - appAgent, gofedAgent string, + appAgent string, clock Clock, signer httpsig.Signer, pubKeyId string, @@ -63,7 +84,7 @@ func NewHttpSigTransport( return &HttpSigTransport{ client: client, appAgent: appAgent, - gofedAgent: gofedAgent, + gofedAgent: goFedUserAgent(), clock: clock, signer: signer, pubKeyId: pubKeyId, @@ -71,7 +92,8 @@ func NewHttpSigTransport( } } -// Dereferences with a request signed with an HTTP Signature. +// Dereference sends a GET request signed with an HTTP Signature to obtain an +// ActivityStreams value. func (h HttpSigTransport) Dereference(c context.Context, iri *url.URL) ([]byte, error) { req, err := http.NewRequest("GET", iri.String(), nil) if err != nil {