summaryrefslogtreecommitdiff
path: root/vendor/github.com/superseriousbusiness/activity/pub/transport.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/superseriousbusiness/activity/pub/transport.go')
-rw-r--r--vendor/github.com/superseriousbusiness/activity/pub/transport.go207
1 files changed, 207 insertions, 0 deletions
diff --git a/vendor/github.com/superseriousbusiness/activity/pub/transport.go b/vendor/github.com/superseriousbusiness/activity/pub/transport.go
new file mode 100644
index 000000000..bdc58a97a
--- /dev/null
+++ b/vendor/github.com/superseriousbusiness/activity/pub/transport.go
@@ -0,0 +1,207 @@
+package pub
+
+import (
+ "bytes"
+ "context"
+ "crypto"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strings"
+ "sync"
+
+ "github.com/go-fed/httpsig"
+)
+
+const (
+ // acceptHeaderValue is the Accept header value indicating that the
+ // response should contain an ActivityStreams object.
+ acceptHeaderValue = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
+)
+
+// isSuccess returns true if the HTTP status code is either OK, Created, or
+// Accepted.
+func isSuccess(code int) bool {
+ return code == http.StatusOK ||
+ code == http.StatusCreated ||
+ code == http.StatusAccepted
+}
+
+// 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 {
+ // Dereference fetches the ActivityStreams object located at this IRI
+ // with a GET request.
+ Dereference(c context.Context, iri *url.URL) ([]byte, error)
+ // Deliver sends an ActivityStreams object.
+ Deliver(c context.Context, b []byte, to *url.URL) error
+ // BatchDeliver sends an ActivityStreams object to multiple recipients.
+ BatchDeliver(c context.Context, b []byte, recipients []*url.URL) error
+}
+
+// Transport must be implemented by HttpSigTransport.
+var _ Transport = &HttpSigTransport{}
+
+// HttpSigTransport makes a dereference call using HTTP signatures to
+// authenticate the request on behalf of a particular actor.
+//
+// No rate limiting is applied.
+//
+// Only one request is tried per call.
+type HttpSigTransport struct {
+ client HttpClient
+ appAgent string
+ gofedAgent string
+ clock Clock
+ getSigner httpsig.Signer
+ getSignerMu *sync.Mutex
+ postSigner httpsig.Signer
+ postSignerMu *sync.Mutex
+ pubKeyId string
+ privKey crypto.PrivateKey
+}
+
+// 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 string,
+ clock Clock,
+ getSigner, postSigner httpsig.Signer,
+ pubKeyId string,
+ privKey crypto.PrivateKey) *HttpSigTransport {
+ return &HttpSigTransport{
+ client: client,
+ appAgent: appAgent,
+ gofedAgent: goFedUserAgent(),
+ clock: clock,
+ getSigner: getSigner,
+ getSignerMu: &sync.Mutex{},
+ postSigner: postSigner,
+ postSignerMu: &sync.Mutex{},
+ pubKeyId: pubKeyId,
+ privKey: privKey,
+ }
+}
+
+// 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 {
+ return nil, err
+ }
+ req = req.WithContext(c)
+ req.Header.Add(acceptHeader, acceptHeaderValue)
+ req.Header.Add("Accept-Charset", "utf-8")
+ req.Header.Add("Date", h.clock.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
+ req.Header.Add("User-Agent", fmt.Sprintf("%s %s", h.appAgent, h.gofedAgent))
+ req.Header.Set("Host", iri.Host)
+ h.getSignerMu.Lock()
+ err = h.getSigner.SignRequest(h.privKey, h.pubKeyId, req, nil)
+ h.getSignerMu.Unlock()
+ if err != nil {
+ return nil, err
+ }
+ resp, err := h.client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("GET request to %s failed (%d): %s", iri.String(), resp.StatusCode, resp.Status)
+ }
+ return ioutil.ReadAll(resp.Body)
+}
+
+// Deliver sends a POST request with an HTTP Signature.
+func (h HttpSigTransport) Deliver(c context.Context, b []byte, to *url.URL) error {
+ req, err := http.NewRequest("POST", to.String(), bytes.NewReader(b))
+ if err != nil {
+ return err
+ }
+ req = req.WithContext(c)
+ req.Header.Add(contentTypeHeader, contentTypeHeaderValue)
+ req.Header.Add("Accept-Charset", "utf-8")
+ req.Header.Add("Date", h.clock.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
+ req.Header.Add("User-Agent", fmt.Sprintf("%s %s", h.appAgent, h.gofedAgent))
+ req.Header.Set("Host", to.Host)
+ h.postSignerMu.Lock()
+ err = h.postSigner.SignRequest(h.privKey, h.pubKeyId, req, b)
+ h.postSignerMu.Unlock()
+ if err != nil {
+ return err
+ }
+ resp, err := h.client.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ if !isSuccess(resp.StatusCode) {
+ return fmt.Errorf("POST request to %s failed (%d): %s", to.String(), resp.StatusCode, resp.Status)
+ }
+ return nil
+}
+
+// BatchDeliver sends concurrent POST requests. Returns an error if any of the
+// requests had an error.
+func (h HttpSigTransport) BatchDeliver(c context.Context, b []byte, recipients []*url.URL) error {
+ var wg sync.WaitGroup
+ errCh := make(chan error, len(recipients))
+ for _, recipient := range recipients {
+ wg.Add(1)
+ go func(r *url.URL) {
+ defer wg.Done()
+ if err := h.Deliver(c, b, r); err != nil {
+ errCh <- err
+ }
+ }(recipient)
+ }
+ wg.Wait()
+ errs := make([]string, 0, len(recipients))
+outer:
+ for {
+ select {
+ case e := <-errCh:
+ errs = append(errs, e.Error())
+ default:
+ break outer
+ }
+ }
+ if len(errs) > 0 {
+ return fmt.Errorf("batch deliver had at least one failure: %s", strings.Join(errs, "; "))
+ }
+ return nil
+}
+
+// HttpClient sends http requests, and is an abstraction only needed by the
+// HttpSigTransport. The standard library's Client satisfies this interface.
+type HttpClient interface {
+ Do(req *http.Request) (*http.Response, error)
+}
+
+// HttpClient must be implemented by http.Client.
+var _ HttpClient = &http.Client{}