123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- // Copyright 2022 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package activitypub
-
- import (
- "bytes"
- "context"
- "crypto/rsa"
- "crypto/x509"
- "encoding/pem"
- "fmt"
- "net/http"
- "strings"
- "time"
-
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/proxy"
- "code.gitea.io/gitea/modules/setting"
-
- "github.com/go-fed/httpsig"
- )
-
- const (
- // ActivityStreamsContentType const
- ActivityStreamsContentType = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
- httpsigExpirationTime = 60
- )
-
- // Gets the current time as an RFC 2616 formatted string
- // RFC 2616 requires RFC 1123 dates but with GMT instead of UTC
- func CurrentTime() string {
- return strings.ReplaceAll(time.Now().UTC().Format(time.RFC1123), "UTC", "GMT")
- }
-
- func containsRequiredHTTPHeaders(method string, headers []string) error {
- var hasRequestTarget, hasDate, hasDigest bool
- for _, header := range headers {
- hasRequestTarget = hasRequestTarget || header == httpsig.RequestTarget
- hasDate = hasDate || header == "Date"
- hasDigest = hasDigest || header == "Digest"
- }
- if !hasRequestTarget {
- return fmt.Errorf("missing http header for %s: %s", method, httpsig.RequestTarget)
- } else if !hasDate {
- return fmt.Errorf("missing http header for %s: Date", method)
- } else if !hasDigest && method != http.MethodGet {
- return fmt.Errorf("missing http header for %s: Digest", method)
- }
- return nil
- }
-
- // Client struct
- type Client struct {
- client *http.Client
- algs []httpsig.Algorithm
- digestAlg httpsig.DigestAlgorithm
- getHeaders []string
- postHeaders []string
- priv *rsa.PrivateKey
- pubID string
- }
-
- // NewClient function
- func NewClient(ctx context.Context, user *user_model.User, pubID string) (c *Client, err error) {
- if err = containsRequiredHTTPHeaders(http.MethodGet, setting.Federation.GetHeaders); err != nil {
- return nil, err
- } else if err = containsRequiredHTTPHeaders(http.MethodPost, setting.Federation.PostHeaders); err != nil {
- return nil, err
- }
-
- priv, err := GetPrivateKey(ctx, user)
- if err != nil {
- return nil, err
- }
- privPem, _ := pem.Decode([]byte(priv))
- privParsed, err := x509.ParsePKCS1PrivateKey(privPem.Bytes)
- if err != nil {
- return nil, err
- }
-
- c = &Client{
- client: &http.Client{
- Transport: &http.Transport{
- Proxy: proxy.Proxy(),
- },
- },
- algs: setting.HttpsigAlgs,
- digestAlg: httpsig.DigestAlgorithm(setting.Federation.DigestAlgorithm),
- getHeaders: setting.Federation.GetHeaders,
- postHeaders: setting.Federation.PostHeaders,
- priv: privParsed,
- pubID: pubID,
- }
- return c, err
- }
-
- // NewRequest function
- func (c *Client) NewRequest(b []byte, to string) (req *http.Request, err error) {
- buf := bytes.NewBuffer(b)
- req, err = http.NewRequest(http.MethodPost, to, buf)
- if err != nil {
- return nil, err
- }
- req.Header.Add("Content-Type", ActivityStreamsContentType)
- req.Header.Add("Date", CurrentTime())
- req.Header.Add("User-Agent", "Gitea/"+setting.AppVer)
- signer, _, err := httpsig.NewSigner(c.algs, c.digestAlg, c.postHeaders, httpsig.Signature, httpsigExpirationTime)
- if err != nil {
- return nil, err
- }
- err = signer.SignRequest(c.priv, c.pubID, req, b)
- return req, err
- }
-
- // Post function
- func (c *Client) Post(b []byte, to string) (resp *http.Response, err error) {
- var req *http.Request
- if req, err = c.NewRequest(b, to); err != nil {
- return nil, err
- }
- resp, err = c.client.Do(req)
- return resp, err
- }
|