You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

client.go 3.4KB

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