44package  webhook
55
66import  (
7+ 	"bytes" 
78	"context" 
9+ 	"crypto/hmac" 
10+ 	"crypto/sha256" 
11+ 	"encoding/base64" 
12+ 	"encoding/json" 
813	"fmt" 
914	"net/http" 
1015	"strings" 
16+ 	"time" 
1117
1218	webhook_model "code.gitea.io/gitea/models/webhook" 
1319	"code.gitea.io/gitea/modules/git" 
@@ -16,10 +22,12 @@ import (
1622)
1723
1824type  (
19- 	// FeishuPayload represents 
25+ 	// FeishuPayload represents the payload for Feishu webhook  
2026	FeishuPayload  struct  {
21- 		MsgType  string  `json:"msg_type"`  // text / post / image / share_chat / interactive / file /audio / media 
22- 		Content  struct  {
27+ 		Timestamp  int64   `json:"timestamp,omitempty"`  // Unix timestamp for signature verification 
28+ 		Sign       string  `json:"sign,omitempty"`       // Signature for verification 
29+ 		MsgType    string  `json:"msg_type"`            // text / post / image / share_chat / interactive / file /audio / media 
30+ 		Content    struct  {
2331			Text  string  `json:"text"` 
2432		} `json:"content"` 
2533	}
@@ -178,9 +186,64 @@ func (feishuConvertor) WorkflowJob(p *api.WorkflowJobPayload) (FeishuPayload, er
178186	return  newFeishuTextPayload (text ), nil 
179187}
180188
189+ // GenSign generates a signature for Feishu webhook 
190+ // https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot 
191+ func  GenSign (secret  string , timestamp  int64 ) (string , error ) {
192+ 	// timestamp + key do sha256, then base64 encode 
193+ 	stringToSign  :=  fmt .Sprintf ("%v" , timestamp ) +  "\n "  +  secret 
194+ 
195+ 	h  :=  hmac .New (sha256 .New , []byte (stringToSign ))
196+ 	_ , err  :=  h .Write ([]byte {})
197+ 	if  err  !=  nil  {
198+ 		return  "" , err 
199+ 	}
200+ 
201+ 	signature  :=  base64 .StdEncoding .EncodeToString (h .Sum (nil ))
202+ 	return  signature , nil 
203+ }
204+ 
181205func  newFeishuRequest (_  context.Context , w  * webhook_model.Webhook , t  * webhook_model.HookTask ) (* http.Request , []byte , error ) {
182206	var  pc  payloadConvertor [FeishuPayload ] =  feishuConvertor {}
183- 	return  newJSONRequest (pc , w , t , true )
207+ 	
208+ 	// Get the payload first 
209+ 	payload , err  :=  newPayload (pc , []byte (t .PayloadContent ), t .EventType )
210+ 	if  err  !=  nil  {
211+ 		return  nil , nil , err 
212+ 	}
213+ 	
214+ 	// Add timestamp and signature if secret is provided 
215+ 	if  w .Secret  !=  ""  {
216+ 		timestamp  :=  time .Now ().Unix ()
217+ 		payload .Timestamp  =  timestamp 
218+ 		
219+ 		// Generate signature 
220+ 		sign , err  :=  GenSign (w .Secret , timestamp )
221+ 		if  err  !=  nil  {
222+ 			return  nil , nil , err 
223+ 		}
224+ 		payload .Sign  =  sign 
225+ 	}
226+ 	
227+ 	// Marshal the payload 
228+ 	body , err  :=  json .MarshalIndent (payload , "" , "  " )
229+ 	if  err  !=  nil  {
230+ 		return  nil , nil , err 
231+ 	}
232+ 	
233+ 	// Create the request 
234+ 	method  :=  w .HTTPMethod 
235+ 	if  method  ==  ""  {
236+ 		method  =  http .MethodPost 
237+ 	}
238+ 	
239+ 	req , err  :=  http .NewRequest (method , w .URL , bytes .NewReader (body ))
240+ 	if  err  !=  nil  {
241+ 		return  nil , nil , err 
242+ 	}
243+ 	req .Header .Set ("Content-Type" , "application/json" )
244+ 	
245+ 	// Add default headers 
246+ 	return  req , body , addDefaultHeaders (req , []byte (w .Secret ), w , t , body )
184247}
185248
186249func  init () {
0 commit comments