Update interface definitions and wrapper improvements.

Wrapping default behavior can now be overridden.
This commit is contained in:
Cory Slep 2019-02-15 21:03:43 +01:00
parent bd8220a56c
commit cc83b751a1
11 changed files with 224 additions and 113 deletions

View File

@ -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.

View File

@ -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 {

View File

@ -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.

View File

@ -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.
//

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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.

View File

@ -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 {