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, ) => userFoo passed authentication and is authorized // (, true, false, ) => a user passed authentication but failed authorization (Permission denied) // (, false, false, ) => authentication failed: deny access (Bad request) // (, false, true, ) => authentication failed: must pass HTTP Signature verification or will be Permission Denied // (, true, true, ) => "I don't care, try to validate using HTTP Signatures. If that still doesn't work, permit raw requests access anyway." // (, , , 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. // // Return values are interpreted as follows: // (true, true, ) => user for the outbox passed authentication and is authorized // (true, false, ) => a user passed authentication but failed authorization for this outbox (Permission denied) // (false, true, ) => authentication failed: must pass HTTP Signature verification or will be Permission Denied // (false, false, ) => authentication failed: deny access (Bad request) // (, , error) => an internal error occurred during validation 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, rw RWType) (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, rw RWType) (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, rw RWType) (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, rw RWType) (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) // CanAdd returns true if the provided object is allowed to be added to // the given target collection. Applicable to either or both of the // SocialAPI and FederateAPI. 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. Applicable to either or // both of the SocialAPI and FederateAPI. CanRemove(c context.Context, o vocab.ObjectType, t vocab.ObjectType) bool } // RWType indicates the kind of reading being done. type RWType int const ( // Read indicates the object is only being read. Read RWType = iota // ReadWrite indicates the object is being mutated as well. ReadWrite ) // SocialAPI is provided by users of this library and designed to handle // receiving messages from ActivityPub clients through the Social API. type SocialAPI interface { // 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(c context.Context) 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) } // FederateAPI is provided by users of this library and designed to handle // receiving messages from ActivityPub servers through the Federative API. type FederateAPI interface { // 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) } // SocialApp is an implementation only for the Social API part of the // ActivityPub specification. type SocialApplication interface { Application SocialAPI } // FederateApp is an implementation only for the Federating API part of the // ActivityPub specification. type FederateApplication interface { Application FederateAPI } // SocialFederateApplication is an implementation for both the Social API and // the Federating API parts of the ActivityPub specification. type SocialFederateApplication interface { Application SocialAPI FederateAPI } // 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 AppendType(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) }