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.3KB

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