Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions services/webhook/feishu.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ package webhook

import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"
"strings"
"time"

webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
Expand All @@ -16,10 +20,12 @@ import (
)

type (
// FeishuPayload represents
// FeishuPayload represents the payload for Feishu webhook
FeishuPayload struct {
MsgType string `json:"msg_type"` // text / post / image / share_chat / interactive / file /audio / media
Content struct {
Timestamp int64 `json:"timestamp,omitempty"` // Unix timestamp for signature verification
Sign string `json:"sign,omitempty"` // Signature for verification
MsgType string `json:"msg_type"` // text / post / image / share_chat / interactive / file /audio / media
Content struct {
Text string `json:"text"`
} `json:"content"`
}
Expand Down Expand Up @@ -184,9 +190,29 @@ func (feishuConvertor) WorkflowJob(p *api.WorkflowJobPayload) (FeishuPayload, er
return newFeishuTextPayload(text), nil
}

// feishuGenSign generates a signature for Feishu webhook
// https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
func feishuGenSign(secret string, timestamp int64) string {
// key="{timestamp}\n{secret}", then hmac-sha256, then base64 encode
stringToSign := fmt.Sprintf("%d\n%s", timestamp, secret)
h := hmac.New(sha256.New, []byte(stringToSign))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

func newFeishuRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
var pc payloadConvertor[FeishuPayload] = feishuConvertor{}
return newJSONRequest(pc, w, t, true)
payload, err := newPayload(feishuConvertor{}, []byte(t.PayloadContent), t.EventType)
if err != nil {
return nil, nil, err
}

// Add timestamp and signature if secret is provided
if w.Secret != "" {
timestamp := time.Now().Unix()
payload.Timestamp = timestamp
payload.Sign = feishuGenSign(w.Secret, timestamp)
}

return prepareJSONRequest(payload, w, t, false /* no default headers */)
}

func init() {
Expand Down
4 changes: 4 additions & 0 deletions services/webhook/feishu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,7 @@ func TestFeishuJSONPayload(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "[test/repo:test] \r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", body.Content.Text)
}

func TestFeishuGenSign(t *testing.T) {
assert.Equal(t, "rWZ84lcag1x9aBFhn1gtV4ZN+4gme3pilfQNMk86vKg=", feishuGenSign("a", 1))
}
3 changes: 3 additions & 0 deletions services/webhook/payloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ func newJSONRequest[T any](pc payloadConvertor[T], w *webhook_model.Webhook, t *
if err != nil {
return nil, nil, err
}
return prepareJSONRequest(payload, w, t, withDefaultHeaders)
}

func prepareJSONRequest[T any](payload T, w *webhook_model.Webhook, t *webhook_model.HookTask, withDefaultHeaders bool) (*http.Request, []byte, error) {
body, err := json.MarshalIndent(payload, "", " ")
if err != nil {
return nil, nil, err
Expand Down
Loading