activity/pub/interfaces.go
Cory Slep 2986f7601b Add function to serve ActivityPub object.
This also incorporates some interface changes to permit both HTTP
Signatures and other forms of authentication and authorization when
accessing data. This means support for something like OAuth2 should be
doable in conjunction with HTTP Signatures.

Tests are broken; this commit should not be used as a build point.
2018-05-25 00:23:50 +02:00

367 行
16 KiB
Go

package pub
import (
"context"
"crypto"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/vocab"
"github.com/go-fed/httpsig"
"net/http"
"net/url"
"time"
)
// HandlerFunc returns true if it was able to handle the request as an
// ActivityPub request. If it handled the request then the error should be
// checked. The response will have already been written to when handled and
// there was no error. Client applications can freely choose how to handle the
// request if this function does not handle it.
//
// Note that if the handler attempted to handle the request but returned an
// error, it is up to the client application to determine what headers and
// response to send to the requester.
type HandlerFunc func(context.Context, http.ResponseWriter, *http.Request) (bool, error)
// Clock determines the time.
type Clock interface {
Now() time.Time
}
// HttpClient sends http requests.
type HttpClient interface {
Do(req *http.Request) (*http.Response, error)
}
// SocialAPIVerifier will verify incoming requests from clients and is meant to
// encapsulate authentication functionality by standards such as OAuth (RFC
// 6749).
type SocialAPIVerifier interface {
// Verify will determine the authenticated user for the given request,
// returning false if verification fails. If the request is entirely
// missing the required fields in order to authenticate, this function
// must return nil and false for all values to permit attempting
// validation by HTTP Signatures. If there was an internal error
// determining the authentication of the request, it is returned.
//
// Return values are interpreted as follows:
// (userFoo, true, true, <nil>) => userFoo passed authentication and is authorized
// (<any>, true, false, <nil>) => a user passed authentication but failed authorization (Permission denied)
// (<any>, false, false, <nil>) => authentication failed: deny access (Bad request)
// (<nil>, false, true, <nil>) => authentication failed: must pass HTTP Signature verification or will be Permission Denied
// (<nil>, true, true, <nil>) => "I don't care, try to validate using HTTP Signatures. If that still doesn't work, permit raw requests access anyway."
// (<any>, <any>, <any>, error) => an internal error occurred during validation
//
// Be very careful that the 'authenticatedUser' value is non-nil when
// returning 'authn' and 'authz' values of true, or else the library
// will use the most permissive logic instead of the most restrictive as
// outlined above.
Verify(r *http.Request) (authenticatedUser *url.URL, authn, authz bool, err error)
// VerifyForOutbox is the same as Verify, except that the request must
// authenticate the owner of the provided outbox IRI.
VerifyForOutbox(r *http.Request, outbox url.URL) (authn, authz bool, err error)
}
// Application is provided by users of this library in order to implement a
// social-federative-web application.
//
// The contexts provided in these calls are passed through this library without
// modification, allowing implementations to pass-through request-scoped data in
// order to properly handle the request.
type Application interface {
// Owns returns true if the provided id is owned by this server.
Owns(c context.Context, id url.URL) bool
// Get fetches the ActivityStream representation of the given id.
Get(c context.Context, id url.URL) (PubObject, error)
// GetAsVerifiedUser fetches the ActivityStream representation of the
// given id with the provided IRI representing the authenticated user
// making the request.
GetAsVerifiedUser(c context.Context, id, authdUser url.URL) (PubObject, error)
// Has determines if the server already knows about the object or
// Activity specified by the given id.
Has(c context.Context, id url.URL) (bool, error)
// Set should write or overwrite the value of the provided object for
// its 'id'.
Set(c context.Context, o PubObject) error
// GetInbox returns the OrderedCollection inbox of the actor for this
// context. It is up to the implementation to provide the correct
// collection for the kind of authorization given in the request.
GetInbox(c context.Context, r *http.Request) (vocab.OrderedCollectionType, error)
// GetOutbox returns the OrderedCollection inbox of the actor for this
// context. It is up to the implementation to provide the correct
// collection for the kind of authorization given in the request.
GetOutbox(c context.Context, r *http.Request) (vocab.OrderedCollectionType, error)
// NewId takes in a client id token and returns an ActivityStreams IRI
// id for a new Activity posted to the outbox. The object is provided
// as a Typer so clients can use it to decide how to generate the IRI.
NewId(c context.Context, t Typer) url.URL
// GetPublicKey fetches the public key for a user based on the public
// key id. It also determines which algorithm to use to verify the
// signature.
GetPublicKey(c context.Context, publicKeyId string) (pubKey crypto.PublicKey, algo httpsig.Algorithm, user url.URL, err error)
}
// SocialApp is provided by users of this library and designed to handle
// receiving messages from ActivityPub clients through the Social API.
type SocialApp interface {
// CanAdd returns true if the provided object is allowed to be added to
// the given target collection.
CanAdd(c context.Context, o vocab.ObjectType, t vocab.ObjectType) bool
// CanRemove returns true if the provided object is allowed to be
// removed from the given target collection.
CanRemove(c context.Context, o vocab.ObjectType, t vocab.ObjectType) bool
// AddToOutboxResolver(c context.Context) (*streams.Resolver, error)
// ActorIRI returns the actor's IRI associated with the given request.
ActorIRI(c context.Context, r *http.Request) (url.URL, error)
// GetSocialAPIVerifier returns the authentication mechanism used for
// incoming ActivityPub client requests. It is optional and allowed to
// return null.
//
// Note that regardless of what this implementation returns, HTTP
// Signatures is supported natively as a fallback.
GetSocialAPIVerifier() SocialAPIVerifier
// GetPublicKeyForOutbox fetches the public key for a user based on the
// public key id. It also determines which algorithm to use to verify
// the signature.
//
// Note that a key difference from Application's GetPublicKey is that
// this function must make sure that the actor whose boxIRI is passed in
// matches the public key id that is requested, or return an error.
GetPublicKeyForOutbox(c context.Context, publicKeyId string, boxIRI url.URL) (crypto.PublicKey, httpsig.Algorithm, error)
}
// FederateApp is provided by users of this library and designed to handle
// receiving messages from ActivityPub servers through the Federative API.
type FederateApp interface {
// CanAdd returns true if the provided object is allowed to be added to
// the given target collection.
CanAdd(c context.Context, obj vocab.ObjectType, target vocab.ObjectType) bool
// CanRemove returns true if the provided object is allowed to be added to
// the given target collection.
CanRemove(c context.Context, obj vocab.ObjectType, target vocab.ObjectType) bool
// OnFollow determines whether to take any automatic reactions in
// response to this follow. Note that if this application does not own
// an object on the activity, then the 'AutomaticAccept' and
// 'AutomaticReject' results will behave as if they were 'DoNothing'.
OnFollow(c context.Context, s *streams.Follow) FollowResponse
// Unblocked should return an error if the provided actor ids are not
// able to interact with this particular end user due to being blocked
// or other application-specific logic. This error is passed
// transparently back to the request thread via PostInbox.
//
// If nil error is returned, then the received activity is processed as
// a normal unblocked interaction.
Unblocked(c context.Context, actorIRIs []url.URL) error
// FilterForwarding is invoked when a received activity needs to be
// forwarded to specific inboxes owned by this server in order to avoid
// the ghost reply problem. The IRIs provided are collections owned by
// this server that the federate peer requested inbox forwarding to.
//
// Implementors must apply some sort of filtering to the provided IRI
// collections. Without any filtering, the resulting application is
// vulnerable to becoming a spam bot for a malicious federate peer.
// Typical implementations will filter the iris down to be only the
// follower collections owned by the actors targeted in the activity.
FilterForwarding(c context.Context, activity vocab.ActivityType, iris []url.URL) ([]url.URL, error)
// NewSigner returns a new httpsig.Signer for which deliveries can be
// signed by the actor delivering the Activity. Let me take this moment
// to really level with you, dear anonymous reader-of-documentation. You
// want to use httpsig.RSA_SHA256 as the algorithm. Otherwise, your app
// will not federate correctly and peers will reject the signatures. All
// other known implementations using HTTP Signatures use RSA_SHA256,
// hardcoded just like your implementation will be.
//
// Some people might think it funny to split the federation and use
// their own algorithm. And while I give you the power to build the
// largest foot-gun possible to blow away your limbs because I respect
// your freedom, you as a developer have the responsibility to also be
// cognizant of the wider community you are building for. Don't be a
// dick.
//
// The headers available for inclusion in the signature are:
// Date
// User-Agent
NewSigner() (httpsig.Signer, error)
// PrivateKey fetches the private key and its associated public key ID.
// The given URL is the inbox or outbox for the actor whose key is
// needed.
PrivateKey(boxIRI url.URL) (privKey crypto.PrivateKey, pubKeyId string, err error)
}
// FollowResponse instructs how to proceed upon immediately receiving a request
// to follow.
type FollowResponse int
const (
AutomaticAccept FollowResponse = iota
AutomaticReject
DoNothing
)
// Callbacker provides an Application hooks into the lifecycle of the
// ActivityPub processes for both client-to-server and server-to-server
// interactions. These callbacks are called after their spec-compliant actions
// are completed, but before inbox forwarding and before delivery.
//
// Note that at minimum, for inbox forwarding to work correctly, these
// Activities must be stored in the client application as a system of record.
//
// Note that modifying the ActivityStream objects in a callback may cause
// unintentionally non-standard behavior if modifying core attributes, but
// otherwise affords clients powerful flexibility. Use responsibly.
type Callbacker interface {
// Create Activity callback.
Create(c context.Context, s *streams.Create) error
// Update Activity callback.
Update(c context.Context, s *streams.Update) error
// Delete Activity callback.
Delete(c context.Context, s *streams.Delete) error
// Add Activity callback.
Add(c context.Context, s *streams.Add) error
// Remove Activity callback.
Remove(c context.Context, s *streams.Remove) error
// Like Activity callback.
Like(c context.Context, s *streams.Like) error
// Block Activity callback. By default, this implmentation does not
// dictate how blocking should be implemented, so it is up to the
// application to enforce this by implementing the FederateApp
// interface.
Block(c context.Context, s *streams.Block) error
// Follow Activity callback. In the special case of server-to-server
// delivery of a Follow activity, this implementation supports the
// option of automatically replying with an 'Accept', 'Reject', or
// waiting for human interaction as provided in the FederateApp
// interface.
//
// In the special case that the FederateApp returned AutomaticAccept,
// this library automatically handles adding the 'actor' to the
// 'followers' collection of the 'object'.
Follow(c context.Context, s *streams.Follow) error
// Undo Activity callback. It is up to the client to provide support
// for all 'Undo' operations; this implementation does not attempt to
// provide a generic implementation.
Undo(c context.Context, s *streams.Undo) error
// Accept Activity callback. In the special case that this 'Accept'
// activity has an 'object' of 'Follow' type, then the library will
// handle adding the 'actor' to the 'following' collection of the
// original 'actor' who requested the 'Follow'.
Accept(c context.Context, s *streams.Accept) error
// Reject Activity callback. Note that in the special case that this
// 'Reject' activity has an 'object' of 'Follow' type, then the client
// MUST NOT add the 'actor' to the 'following' collection of the
// original 'actor' who requested the 'Follow'.
Reject(c context.Context, s *streams.Reject) error
}
// Deliverer schedules federated ActivityPub messages for delivery, possibly
// asynchronously.
type Deliverer interface {
// Do schedules a message to be sent to a specific URL endpoint by
// using toDo.
Do(b []byte, to url.URL, toDo func(b []byte, u url.URL) error)
}
// PubObject is an ActivityPub Object.
type PubObject interface {
vocab.Serializer
Typer
GetId() url.URL
SetId(url.URL)
HasId() bool
AddType(interface{})
RemoveType(int)
}
// Typer is an object that has a type.
type Typer interface {
TypeLen() (l int)
GetType(index int) (v interface{})
}
// typeIder is a Typer with additional generic capabilities.
type typeIder interface {
Typer
SetId(v url.URL)
Serialize() (m map[string]interface{}, e error)
}
// actor is an object that is an ActivityPub Actor. The specification is more
// strict than what we include here, only for our internal use.
type actor interface {
IsInboxAnyURI() (ok bool)
GetInboxAnyURI() (v url.URL)
IsInboxOrderedCollection() (ok bool)
GetInboxOrderedCollection() (v vocab.OrderedCollectionType)
}
var _ actor = &vocab.Object{}
// actorObject is an object that has "actor" or "attributedTo" properties,
// representing the author or originator of the object.
type actorObject interface {
IsInboxAnyURI() (ok bool)
GetInboxAnyURI() (v url.URL)
IsInboxOrderedCollection() (ok bool)
GetInboxOrderedCollection() (v vocab.OrderedCollectionType)
AttributedToLen() (l int)
IsAttributedToObject(index int) (ok bool)
GetAttributedToObject(index int) (v vocab.ObjectType)
IsAttributedToLink(index int) (ok bool)
GetAttributedToLink(index int) (v vocab.LinkType)
IsAttributedToIRI(index int) (ok bool)
GetAttributedToIRI(index int) (v url.URL)
ActorLen() (l int)
IsActorObject(index int) (ok bool)
GetActorObject(index int) (v vocab.ObjectType)
IsActorLink(index int) (ok bool)
GetActorLink(index int) (v vocab.LinkType)
IsActorIRI(index int) (ok bool)
GetActorIRI(index int) (v url.URL)
}
// deliverableObject is an object that is able to be sent to recipients via the
// "to", "bto", "cc", "bcc", and "audience" objects and/or links and/or IRIs.
type deliverableObject interface {
actorObject
ToLen() (l int)
IsToObject(index int) (ok bool)
GetToObject(index int) (v vocab.ObjectType)
IsToLink(index int) (ok bool)
GetToLink(index int) (v vocab.LinkType)
IsToIRI(index int) (ok bool)
GetToIRI(index int) (v url.URL)
BtoLen() (l int)
IsBtoObject(index int) (ok bool)
GetBtoObject(index int) (v vocab.ObjectType)
RemoveBtoObject(index int)
IsBtoLink(index int) (ok bool)
GetBtoLink(index int) (v vocab.LinkType)
RemoveBtoLink(index int)
IsBtoIRI(index int) (ok bool)
GetBtoIRI(index int) (v url.URL)
RemoveBtoIRI(index int)
CcLen() (l int)
IsCcObject(index int) (ok bool)
GetCcObject(index int) (v vocab.ObjectType)
IsCcLink(index int) (ok bool)
GetCcLink(index int) (v vocab.LinkType)
IsCcIRI(index int) (ok bool)
GetCcIRI(index int) (v url.URL)
BccLen() (l int)
IsBccObject(index int) (ok bool)
GetBccObject(index int) (v vocab.ObjectType)
RemoveBccObject(index int)
IsBccLink(index int) (ok bool)
GetBccLink(index int) (v vocab.LinkType)
RemoveBccLink(index int)
IsBccIRI(index int) (ok bool)
GetBccIRI(index int) (v url.URL)
RemoveBccIRI(index int)
AudienceLen() (l int)
IsAudienceObject(index int) (ok bool)
GetAudienceObject(index int) (v vocab.ObjectType)
IsAudienceLink(index int) (ok bool)
GetAudienceLink(index int) (v vocab.LinkType)
IsAudienceIRI(index int) (ok bool)
GetAudienceIRI(index int) (v url.URL)
}