Some checks are pending
Security Checks / security (push) Waiting to run
将旧Telegram兼容入口收口到当前账户绑定流程,禁止请求体伪造Chat ID,并补充超级管理员本人插件设置权限回归用例。
188 lines
8.0 KiB
Go
188 lines
8.0 KiB
Go
package controllers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.pcux.cn/xuans/Smail/db/migration"
|
|
"git.pcux.cn/xuans/Smail/hooks"
|
|
"git.pcux.cn/xuans/Smail/hooks/framework"
|
|
"git.pcux.cn/xuans/Smail/models"
|
|
pluginservice "git.pcux.cn/xuans/Smail/services/plugin"
|
|
appcontext "git.pcux.cn/xuans/Smail/utils/context"
|
|
"git.pcux.cn/xuans/Smail/utils/credential"
|
|
_ "modernc.org/sqlite"
|
|
"xorm.io/xorm"
|
|
)
|
|
|
|
type telegramControllerTestHook struct {
|
|
pluginTestHook
|
|
botInfo framework.BotInfo
|
|
botInfoErr error
|
|
testPayload json.RawMessage
|
|
testMessageErr error
|
|
userID int
|
|
userSettings json.RawMessage
|
|
}
|
|
|
|
func (h *telegramControllerTestHook) ConfigureUserSettings(userID int, settings json.RawMessage) error {
|
|
h.userID = userID
|
|
h.userSettings = append(h.userSettings[:0], settings...)
|
|
return nil
|
|
}
|
|
|
|
func (h *telegramControllerTestHook) GetBotInfo() (framework.BotInfo, error) {
|
|
return h.botInfo, h.botInfoErr
|
|
}
|
|
|
|
func (h *telegramControllerTestHook) TestMessage(settings json.RawMessage) error {
|
|
h.testPayload = append(h.testPayload[:0], settings...)
|
|
return h.testMessageErr
|
|
}
|
|
|
|
func TestTelegramUserSettingsRequiresLogin(t *testing.T) {
|
|
request := httptest.NewRequest(http.MethodGet, "/api/v1/plugin/settings/telegram_push/user", nil)
|
|
recorder := httptest.NewRecorder()
|
|
GetTelegramUserSettings(nil, recorder, request)
|
|
if recorder.Code != http.StatusForbidden || !strings.Contains(recorder.Body.String(), "无权访问或请求不满足安全条件") {
|
|
t.Fatalf("unauthenticated GET status=%d body=%s", recorder.Code, recorder.Body.String())
|
|
}
|
|
|
|
request = httptest.NewRequest(http.MethodPut, "/api/v1/plugin/settings/telegram_push/user", strings.NewReader(`{}`))
|
|
recorder = httptest.NewRecorder()
|
|
UpdateTelegramUserSettings(&appcontext.Context{}, recorder, request)
|
|
if recorder.Code != http.StatusForbidden || !strings.Contains(recorder.Body.String(), "无权访问或请求不满足安全条件") {
|
|
t.Fatalf("unauthenticated PUT status=%d body=%s", recorder.Code, recorder.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestTelegramUserSettingsRejectsUnknownAndTrailingFields(t *testing.T) {
|
|
service, engine := newTelegramControllerConfigService(t)
|
|
previousService := hooks.PluginConfigService()
|
|
hooks.SetPluginConfigService(service)
|
|
t.Cleanup(func() { hooks.SetPluginConfigService(previousService) })
|
|
user := models.User{Account: "telegram-parse-user", Name: "telegram-parse-user", Password: "hash"}
|
|
if _, err := engine.Insert(&user); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cases := []string{
|
|
`{"enabled":false,"chatId":"123","version":0}`,
|
|
`{"enabled":false,"version":0} {}`,
|
|
}
|
|
for _, body := range cases {
|
|
recorder := httptest.NewRecorder()
|
|
request := httptest.NewRequest(http.MethodPut, "/api/v1/plugin/settings/telegram_push/user", bytes.NewBufferString(body))
|
|
UpdateTelegramUserSettings(&appcontext.Context{UserID: user.ID, RecentAuth: true}, recorder, request)
|
|
if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), "请求参数格式不正确") {
|
|
t.Fatalf("body=%s status=%d response=%s", body, recorder.Code, recorder.Body.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTelegramUserSettingsUsesSessionUserAndVersion(t *testing.T) {
|
|
service, engine := newTelegramControllerConfigService(t)
|
|
previousService := hooks.PluginConfigService()
|
|
hooks.SetPluginConfigService(service)
|
|
t.Cleanup(func() { hooks.SetPluginConfigService(previousService) })
|
|
|
|
user := models.User{Account: "controller-user", Name: "controller-user", Password: "hash"}
|
|
if _, err := engine.Insert(&user); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
firstRecorder := httptest.NewRecorder()
|
|
firstRequest := httptest.NewRequest(http.MethodPut, "/api/v1/plugin/settings/telegram_push/user", strings.NewReader(`{"enabled":false,"showContent":true,"version":0}`))
|
|
UpdateTelegramUserSettings(&appcontext.Context{UserID: user.ID, RecentAuth: true}, firstRecorder, firstRequest)
|
|
if firstRecorder.Code != http.StatusOK || !strings.Contains(firstRecorder.Body.String(), `"version":1`) {
|
|
t.Fatalf("first update status=%d body=%s", firstRecorder.Code, firstRecorder.Body.String())
|
|
}
|
|
if strings.Contains(firstRecorder.Body.String(), "bot-token") {
|
|
t.Fatal("secret material appeared in settings response")
|
|
}
|
|
|
|
staleRecorder := httptest.NewRecorder()
|
|
staleRequest := httptest.NewRequest(http.MethodPut, "/api/v1/plugin/settings/telegram_push/user", strings.NewReader(`{"enabled":false,"showContent":false,"version":0}`))
|
|
UpdateTelegramUserSettings(&appcontext.Context{UserID: user.ID, RecentAuth: true}, staleRecorder, staleRequest)
|
|
if staleRecorder.Code != http.StatusOK || !strings.Contains(staleRecorder.Body.String(), "配置已被其他操作更新") {
|
|
t.Fatalf("stale update status=%d body=%s", staleRecorder.Code, staleRecorder.Body.String())
|
|
}
|
|
|
|
getRecorder := httptest.NewRecorder()
|
|
GetTelegramUserSettings(&appcontext.Context{UserID: user.ID}, getRecorder, httptest.NewRequest(http.MethodGet, "/api/v1/plugin/settings/telegram_push/user", nil))
|
|
if getRecorder.Code != http.StatusOK || !strings.Contains(getRecorder.Body.String(), `"bound":false`) || strings.Contains(getRecorder.Body.String(), "-1001234567890") {
|
|
t.Fatalf("GET status=%d body=%s", getRecorder.Code, getRecorder.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestTelegramBotInfoDoesNotExposeSecret(t *testing.T) {
|
|
previousHooks := hooks.HookList
|
|
hook := &telegramControllerTestHook{botInfo: framework.BotInfo{Username: "smail_test_bot", BotLink: "https://t.me/smail_test_bot"}}
|
|
hooks.HookList = map[string]framework.EmailHook{"telegram_push": hook}
|
|
t.Cleanup(func() { hooks.HookList = previousHooks })
|
|
|
|
recorder := httptest.NewRecorder()
|
|
GetTelegramBotInfo(&appcontext.Context{UserID: 7}, recorder, httptest.NewRequest(http.MethodGet, "/api/v1/plugin/settings/telegram_push/bot", nil))
|
|
if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), "smail_test_bot") {
|
|
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
|
}
|
|
if strings.Contains(recorder.Body.String(), "bot-token") || strings.Contains(recorder.Body.String(), "secret") {
|
|
t.Fatalf("bot response exposed secret material: %s", recorder.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestTelegramTestMessageRequiresCurrentAccountBinding(t *testing.T) {
|
|
service, engine := newTelegramControllerConfigService(t)
|
|
previousService := hooks.PluginConfigService()
|
|
hooks.SetPluginConfigService(service)
|
|
t.Cleanup(func() { hooks.SetPluginConfigService(previousService) })
|
|
|
|
user := models.User{Account: "telegram-test-user", Name: "telegram-test-user", Password: "hash"}
|
|
if _, err := engine.Insert(&user); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
previousHooks := hooks.HookList
|
|
hook := &telegramControllerTestHook{}
|
|
hooks.HookList = map[string]framework.EmailHook{"telegram_push": hook}
|
|
t.Cleanup(func() { hooks.HookList = previousHooks })
|
|
|
|
recorder := httptest.NewRecorder()
|
|
request := httptest.NewRequest(http.MethodPost, "/api/v1/plugin/settings/telegram_push/test", strings.NewReader(`{"settings":{"chatId":"123"}}`))
|
|
TestTelegramMessage(&appcontext.Context{UserID: user.ID, RecentAuth: true}, recorder, request)
|
|
if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), "请先绑定本人 Telegram 并启用通知") {
|
|
t.Fatalf("unbound test message status=%d body=%s", recorder.Code, recorder.Body.String())
|
|
}
|
|
if hook.testPayload != nil {
|
|
t.Fatal("caller-supplied Chat ID reached plugin without a verified binding")
|
|
}
|
|
}
|
|
|
|
func newTelegramControllerConfigService(t *testing.T) (*pluginservice.ConfigService, *xorm.Engine) {
|
|
t.Helper()
|
|
engine, err := xorm.NewEngine("sqlite", filepath.Join(t.TempDir(), "telegram-controller.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = engine.Close() })
|
|
runner := migration.Runner{DB: engine.DB().DB, Dialect: migration.DialectSQLite, Migrations: migration.DefaultMigrations()}
|
|
if _, err = runner.Run(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
vault, err := credential.NewVault(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
service, err := pluginservice.NewConfigService(engine, vault)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return service, engine
|
|
}
|