Files
BaaRai e233c02314 Translate all Chinese text to English
Comments, doc comments, UI labels, log and error messages, LLM prompt
strings, markdown docs, test data and assertions. Identifiers, enum
keys, routes, placeholders and code structure are unchanged.

Notes:
- docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md
- tests that exercised CJK specific handling (GBK zip names, Chinese
  full text search) now use ASCII equivalents; go build passes and the
  test suite matches the pre translation baseline (remaining failures
  need a PostgreSQL DSN and fail identically on the original)
- frontend mention syntax now uses English kinds (finding, asset, ...)
  matching the backend, chat mentions tests pass
2026-10-08 23:14:34 +02:00

101 lines
3.7 KiB
Go

package notify
import (
"net/url"
"testing"
"time"
)
// The signature reference values were computed independently with OpenSSL, not
// generated by this package's own implementation — otherwise they could only
// prove "the code has not changed", not "the algorithm is right".
//
// TS=1700000000000, SECRET=SECtest123
// DingTalk: printf '%s\n%s' "$TS" "$SECRET" | openssl dgst -sha256 -hmac "$SECRET" -binary | openssl base64 -A
// -> w3RMHXzixTMdzr8OHJUmVLS4IoPJVdu+Ut1LE48MePE=
// Feishu: printf '' | openssl dgst -sha256 -hmac "$(printf '%s\n%s' "$TS" "$SECRET")" -binary | openssl base64 -A
// -> Hd4xFWQU6R6ad4nzy4ETIznzlqebqH7xcTFVmONTudo=
const (
signTestTSMillis = int64(1700000000000)
signTestSecret = "SECtest123"
dingTalkExpected = "w3RMHXzixTMdzr8OHJUmVLS4IoPJVdu+Ut1LE48MePE="
feishuExpected = "Hd4xFWQU6R6ad4nzy4ETIznzlqebqH7xcTFVmONTudo="
)
func TestDingTalkSignMatchesReference(t *testing.T) {
got, err := dingTalkSignedURL("https://oapi.dingtalk.com/robot/send?access_token=tok", signTestSecret, time.UnixMilli(signTestTSMillis))
if err != nil {
t.Fatalf("signing failed: %v", err)
}
u, err := url.Parse(got)
if err != nil {
t.Fatalf("the produced address cannot be parsed: %v", err)
}
q := u.Query()
if q.Get("sign") != dingTalkExpected {
t.Errorf("signature mismatch\nwant %s\ngot %s", dingTalkExpected, q.Get("sign"))
}
if q.Get("timestamp") != "1700000000000" {
t.Errorf("the timestamp should be in milliseconds and carried as-is, got %q", q.Get("timestamp"))
}
// The original query parameter (access_token) must not be overwritten by the
// signature.
if q.Get("access_token") != "tok" {
t.Errorf("the original query parameter was lost, got %q", q.Get("access_token"))
}
}
func TestFeishuSignMatchesReference(t *testing.T) {
got := feishuSign("1700000000000", signTestSecret)
if got != feishuExpected {
t.Errorf("signature mismatch\nwant %s\ngot %s", feishuExpected, got)
}
}
// TestSignAlgorithmsDiffer locks the difference between the two platforms'
// algorithms. They happen to be mirror images of each other's parameter order
// (DingTalk key=secret, Feishu key=string-to-sign), and copying from the other
// platform always fails verification; this case ensures a future refactor does
// not merge the two into one function.
func TestSignAlgorithmsDiffer(t *testing.T) {
ts := "1700000000000"
dingURL, err := dingTalkSignedURL("https://example.com/hook", signTestSecret, time.UnixMilli(signTestTSMillis))
if err != nil {
t.Fatal(err)
}
dq, _ := url.Parse(dingURL)
if dq.Query().Get("sign") == feishuSign(ts, signTestSecret) {
t.Fatal("DingTalk and Feishu signatures are identical, which means one of the two algorithm implementations is wrong")
}
}
func TestDingTalkNoSecretLeavesURLUntouched(t *testing.T) {
// A bot without signing enabled: the timestamp/sign parameters must not be
// added out of thin air.
const hook = "https://oapi.dingtalk.com/robot/send?access_token=tok"
got, err := dingTalkSignedURL(hook, "", time.UnixMilli(signTestTSMillis))
if err != nil {
t.Fatal(err)
}
if got != hook {
t.Fatalf("the address should not change when no secret is configured, got %q", got)
}
}
func TestValidateHTTPURL(t *testing.T) {
ok := []string{"https://example.com/hook", "http://10.0.0.1:8080/x?y=1"}
for _, s := range ok {
if err := validateHTTPURL(s); err != nil {
t.Errorf("%q should be accepted: %v", s, err)
}
}
// file:// and the like must not be let through — http.Client's handling of
// them is beyond the expected scope.
bad := []string{"", "file:///etc/passwd", "ftp://example.com", "https://", "gopher://x"}
for _, s := range bad {
if err := validateHTTPURL(s); err == nil {
t.Errorf("%q should be rejected", s)
}
}
}