code.gitea.io/gitea@v1.19.3/modules/activitypub/client.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package activitypub
     5  
     6  import (
     7  	"bytes"
     8  	"crypto/rsa"
     9  	"crypto/x509"
    10  	"encoding/pem"
    11  	"fmt"
    12  	"net/http"
    13  	"strings"
    14  	"time"
    15  
    16  	user_model "code.gitea.io/gitea/models/user"
    17  	"code.gitea.io/gitea/modules/proxy"
    18  	"code.gitea.io/gitea/modules/setting"
    19  
    20  	"github.com/go-fed/httpsig"
    21  )
    22  
    23  const (
    24  	// ActivityStreamsContentType const
    25  	ActivityStreamsContentType = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
    26  	httpsigExpirationTime      = 60
    27  )
    28  
    29  // Gets the current time as an RFC 2616 formatted string
    30  // RFC 2616 requires RFC 1123 dates but with GMT instead of UTC
    31  func CurrentTime() string {
    32  	return strings.ReplaceAll(time.Now().UTC().Format(time.RFC1123), "UTC", "GMT")
    33  }
    34  
    35  func containsRequiredHTTPHeaders(method string, headers []string) error {
    36  	var hasRequestTarget, hasDate, hasDigest bool
    37  	for _, header := range headers {
    38  		hasRequestTarget = hasRequestTarget || header == httpsig.RequestTarget
    39  		hasDate = hasDate || header == "Date"
    40  		hasDigest = hasDigest || header == "Digest"
    41  	}
    42  	if !hasRequestTarget {
    43  		return fmt.Errorf("missing http header for %s: %s", method, httpsig.RequestTarget)
    44  	} else if !hasDate {
    45  		return fmt.Errorf("missing http header for %s: Date", method)
    46  	} else if !hasDigest && method != http.MethodGet {
    47  		return fmt.Errorf("missing http header for %s: Digest", method)
    48  	}
    49  	return nil
    50  }
    51  
    52  // Client struct
    53  type Client struct {
    54  	client      *http.Client
    55  	algs        []httpsig.Algorithm
    56  	digestAlg   httpsig.DigestAlgorithm
    57  	getHeaders  []string
    58  	postHeaders []string
    59  	priv        *rsa.PrivateKey
    60  	pubID       string
    61  }
    62  
    63  // NewClient function
    64  func NewClient(user *user_model.User, pubID string) (c *Client, err error) {
    65  	if err = containsRequiredHTTPHeaders(http.MethodGet, setting.Federation.GetHeaders); err != nil {
    66  		return
    67  	} else if err = containsRequiredHTTPHeaders(http.MethodPost, setting.Federation.PostHeaders); err != nil {
    68  		return
    69  	}
    70  
    71  	priv, err := GetPrivateKey(user)
    72  	if err != nil {
    73  		return
    74  	}
    75  	privPem, _ := pem.Decode([]byte(priv))
    76  	privParsed, err := x509.ParsePKCS1PrivateKey(privPem.Bytes)
    77  	if err != nil {
    78  		return
    79  	}
    80  
    81  	c = &Client{
    82  		client: &http.Client{
    83  			Transport: &http.Transport{
    84  				Proxy: proxy.Proxy(),
    85  			},
    86  		},
    87  		algs:        setting.HttpsigAlgs,
    88  		digestAlg:   httpsig.DigestAlgorithm(setting.Federation.DigestAlgorithm),
    89  		getHeaders:  setting.Federation.GetHeaders,
    90  		postHeaders: setting.Federation.PostHeaders,
    91  		priv:        privParsed,
    92  		pubID:       pubID,
    93  	}
    94  	return c, err
    95  }
    96  
    97  // NewRequest function
    98  func (c *Client) NewRequest(b []byte, to string) (req *http.Request, err error) {
    99  	buf := bytes.NewBuffer(b)
   100  	req, err = http.NewRequest(http.MethodPost, to, buf)
   101  	if err != nil {
   102  		return
   103  	}
   104  	req.Header.Add("Content-Type", ActivityStreamsContentType)
   105  	req.Header.Add("Date", CurrentTime())
   106  	req.Header.Add("User-Agent", "Gitea/"+setting.AppVer)
   107  	signer, _, err := httpsig.NewSigner(c.algs, c.digestAlg, c.postHeaders, httpsig.Signature, httpsigExpirationTime)
   108  	if err != nil {
   109  		return
   110  	}
   111  	err = signer.SignRequest(c.priv, c.pubID, req, b)
   112  	return req, err
   113  }
   114  
   115  // Post function
   116  func (c *Client) Post(b []byte, to string) (resp *http.Response, err error) {
   117  	var req *http.Request
   118  	if req, err = c.NewRequest(b, to); err != nil {
   119  		return
   120  	}
   121  	resp, err = c.client.Do(req)
   122  	return resp, err
   123  }