удалён вход Telegram, владелец записи стал обязателен в схеме
- убраны клиент бота, транспорт обновлений, отправитель сообщений, сборка входа при старте, секция настроек и зависимость go-telegram-bot-api; из конвейера ушла доставка ответа отправителю — исход виден опросом готовности. Колонки адресата и значение источника остались в схеме: применённые шаги не переписываются - шаг 202608140003 запрещает пустого владельца у аудиозаписи и у файла; существующие строки он не проверяет, и это принято сознательно — искать их надо запросом до выкладки - ревью нашло два пред-существующих дефекта, оба закрыты: пустой второй ответ распознавателя стирал сохранённую расшифровку, а пустая расшифровка перестала быть заметной вместе с убранной доставкой. Попутно поднят golang.org/x/image до v0.45.0 — красный шаг vulns, воспроизводился и на чистом master
This commit is contained in:
@@ -19,7 +19,6 @@ type Config struct {
|
||||
Storage StorageConfig `toml:"storage"`
|
||||
Pipeline PipelineConfig `toml:"pipeline"`
|
||||
Yandex YandexConfig `toml:"yandex"`
|
||||
Telegram TelegramConfig `toml:"telegram"`
|
||||
Auth AuthConfig `toml:"auth"`
|
||||
}
|
||||
|
||||
@@ -61,10 +60,9 @@ func (c PipelineConfig) Validate() error {
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int `toml:"port"`
|
||||
ShutdownTimeout int `toml:"shutdown_timeout"`
|
||||
ForceShutdownTimeout int `toml:"force_shutdown_timeout"`
|
||||
UsersWhiteList []string `toml:"users_while_list"`
|
||||
Port int `toml:"port"`
|
||||
ShutdownTimeout int `toml:"shutdown_timeout"`
|
||||
ForceShutdownTimeout int `toml:"force_shutdown_timeout"`
|
||||
}
|
||||
|
||||
// StorageConfig — единственный каталог данных: под ним лежат и база, и файлы
|
||||
@@ -83,33 +81,6 @@ type YandexConfig struct {
|
||||
ObjStorageEndpoint string `toml:"object_storage_endpoint"`
|
||||
}
|
||||
|
||||
// TelegramConfig — вход Telegram. Признак включения объявляет намерение
|
||||
// владельца, `BotToken` означает только доступ. Пока два значения жили в одном
|
||||
// поле, пустой токен читался разом как «вход выключен» и как «ключ не доехал»,
|
||||
// и сервис поднимался без бота в обоих случаях.
|
||||
type TelegramConfig struct {
|
||||
// Enabled — умолчания у него нет **намеренно**, и потому его нет в
|
||||
// `defaultConfig()`: умолчание было бы угаданным намерением, а признак
|
||||
// заведён затем, чтобы намерение объявляли. Отсутствие ключа в файле ловит
|
||||
// `LoadConfig` — нулевое значение `bool` режима не выбирает.
|
||||
Enabled bool `toml:"enabled"`
|
||||
BotToken string `toml:"bot_token"`
|
||||
UpdateTimeout int `toml:"update_timeout"`
|
||||
}
|
||||
|
||||
// Validate проверяет ключ доступа против объявленного намерения. Пустой ключ
|
||||
// при включённом входе — ошибка настройки: бот по нему не появится, а тихий
|
||||
// подъём без бота оставил бы отправителей без ответов.
|
||||
//
|
||||
// Названо имя ключа, а не значение: значение `bot_token` в журнал попасть не
|
||||
// должно.
|
||||
func (c TelegramConfig) Validate() error {
|
||||
if c.Enabled && c.BotToken == "" {
|
||||
return errors.New("telegram: не заполнен ключ bot_token при enabled = true")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthConfig — вход через внешнего провайдера OIDC. Адреса, идентификатор
|
||||
// клиента и секрет приезжают сюда и приводятся к настройкам коллекции
|
||||
// пользователей при каждом подъёме: применённый шаг схемы не переписывается, и
|
||||
@@ -202,11 +173,6 @@ func defaultConfig() *Config {
|
||||
ObjStorageRegion: "ru-central1",
|
||||
ObjStorageEndpoint: "https://storage.yandexcloud.net/",
|
||||
},
|
||||
// Умолчания у `Enabled` здесь нет намеренно — причина у поля.
|
||||
Telegram: TelegramConfig{
|
||||
BotToken: "",
|
||||
UpdateTimeout: 10,
|
||||
},
|
||||
Auth: AuthConfig{
|
||||
SecureCookie: true,
|
||||
},
|
||||
@@ -223,19 +189,10 @@ func LoadConfig(path string) (*Config, error) {
|
||||
config := defaultConfig()
|
||||
|
||||
// Load configuration from file
|
||||
meta, err := toml.DecodeFile(path, &config)
|
||||
if err != nil {
|
||||
if _, err := toml.DecodeFile(path, &config); err != nil {
|
||||
return nil, decodeError(path, err)
|
||||
}
|
||||
|
||||
// Признак включения входа Telegram обязателен: умолчания у него нет, и
|
||||
// отличить «не задан» от «задан ложным» умеет только разбор — нулевое
|
||||
// значение `bool` в структуре у обоих одинаковое. Отсюда и `meta`: наружу
|
||||
// она не отдаётся, приговор выносится здесь.
|
||||
if !meta.IsDefined("telegram", "enabled") {
|
||||
return nil, errors.New("telegram: не задан ключ enabled; он объявляет, нужен ли сервису вход Telegram")
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
|
||||
+12
-107
@@ -1,7 +1,6 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -100,68 +99,6 @@ func TestAuthConfigValidateRejectsMalformedURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Признак включения объявляет намерение, ключ доступа означает только доступ.
|
||||
// Пока эти два значения жили в одном поле, пустой токен читался разом как
|
||||
// «вход выключен» и как «ключ не доехал».
|
||||
|
||||
func TestTelegramConfigValidateAcceptsEnabledWithToken(t *testing.T) {
|
||||
cfg := TelegramConfig{Enabled: true, BotToken: "123456:AA-fake"}
|
||||
|
||||
if err := cfg.Validate(); err != nil {
|
||||
t.Fatalf("включённый вход с ключом отвергнут: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramConfigValidateRejectsEnabledWithoutToken(t *testing.T) {
|
||||
cfg := TelegramConfig{Enabled: true, BotToken: ""}
|
||||
|
||||
err := cfg.Validate()
|
||||
if err == nil {
|
||||
t.Fatal("включённый вход без ключа доступа пропущен")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "bot_token") {
|
||||
t.Fatalf("имя ключа не названо: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Выключенный вход на ключ доступа не смотрит вовсе: пустой ключ при нём —
|
||||
// обычное состояние локального прогона, а не ошибка настройки.
|
||||
func TestTelegramConfigValidateIgnoresTokenWhenDisabled(t *testing.T) {
|
||||
cfg := TelegramConfig{Enabled: false, BotToken: ""}
|
||||
|
||||
if err := cfg.Validate(); err != nil {
|
||||
t.Fatalf("выключенный вход без ключа отвергнут: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Половина требования «сообщение не несёт значения ключа» на этой проверке
|
||||
// **вакуумна**, и честнее это назвать, чем изображать сторожа.
|
||||
//
|
||||
// `Validate()` отказывает ровно на пустом ключе — значения, которым можно
|
||||
// проговориться, на этом пути не существует. Прежняя редакция сторожа искала
|
||||
// подстроку, которой в сообщении нет ни при каком входе, и потому не могла
|
||||
// упасть вовсе: правка на `%q` от токена оставила бы её зелёной. В проекте это
|
||||
// третий пойманный случай проверки, не способной упасть.
|
||||
//
|
||||
// Настоящий сторож той же нормы живёт там, где непустой ключ в отказ попасть
|
||||
// действительно может, — `TestLoadConfigMalformedSecretLineHidesValue` и
|
||||
// `TestLoadConfigMalformedBeforeAnyKeyHidesValue`. Здесь проверяется то, что
|
||||
// проверяемо: заполненный ключ проходит, пустой отвергается с именем ключа.
|
||||
func TestTelegramConfigValidateNamesKeyWithoutValue(t *testing.T) {
|
||||
filled := TelegramConfig{Enabled: true, BotToken: "123456:AAHfake-secret-token-value"}
|
||||
if err := filled.Validate(); err != nil {
|
||||
t.Fatalf("включённый вход с заполненным ключом отвергнут: %v", err)
|
||||
}
|
||||
|
||||
err := TelegramConfig{Enabled: true, BotToken: ""}.Validate()
|
||||
if err == nil {
|
||||
t.Fatal("включённый вход без ключа доступа пропущен")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "bot_token") {
|
||||
t.Fatalf("имя ключа не названо: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func writeConfig(t *testing.T, body string) string {
|
||||
t.Helper()
|
||||
|
||||
@@ -173,49 +110,17 @@ func writeConfig(t *testing.T, body string) string {
|
||||
}
|
||||
|
||||
const validConfigBody = `
|
||||
[telegram]
|
||||
enabled = false
|
||||
bot_token = ""
|
||||
[storage]
|
||||
data_dir = "data"
|
||||
`
|
||||
|
||||
// Признак обязателен: файл без него негоден. Умолчание было бы угаданным
|
||||
// намерением, а отличить «не задан» от «задан ложным» умеет только разбор —
|
||||
// нулевое значение bool у обоих одинаковое.
|
||||
func TestLoadConfigRejectsMissingTelegramEnabled(t *testing.T) {
|
||||
path := writeConfig(t, "[telegram]\nbot_token = \"123456:AA-fake\"\n")
|
||||
|
||||
_, err := LoadConfig(path)
|
||||
if err == nil {
|
||||
t.Fatal("файл без признака включения принят")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "enabled") {
|
||||
t.Fatalf("имя недостающего ключа не названо: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigReadsBothValuesOfTelegramEnabled(t *testing.T) {
|
||||
for _, enabled := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%t", enabled), func(t *testing.T) {
|
||||
body := fmt.Sprintf("[telegram]\nenabled = %t\nbot_token = \"123456:AA-fake\"\n", enabled)
|
||||
|
||||
cfg, err := LoadConfig(writeConfig(t, body))
|
||||
if err != nil {
|
||||
t.Fatalf("годный файл отвергнут: %v", err)
|
||||
}
|
||||
if cfg.Telegram.Enabled != enabled {
|
||||
t.Fatalf("признак доехал как %t, а в файле %t", cfg.Telegram.Enabled, enabled)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Инвариант «секрет не покидает конфиг»: текст отказа разбора собирает чужая
|
||||
// библиотека из разбираемого куска файла, и оборванная строка ключа доступа
|
||||
// уехала бы в журнал вместе со значением. Отсюда собственное сообщение.
|
||||
func TestLoadConfigMalformedSecretLineHidesValue(t *testing.T) {
|
||||
const secret = "123456:AAHfake-secret-token-value"
|
||||
const secret = "AQVNfake-secret-api-key-value"
|
||||
// Кавычка не закрыта: разбор оборвётся на значении.
|
||||
path := writeConfig(t, "[telegram]\nenabled = true\nbot_token = \""+secret+"\n")
|
||||
path := writeConfig(t, "[yandex]\nfolder_id = \"b1g\"\nspeech_kit_api_key = \""+secret+"\n")
|
||||
|
||||
_, err := LoadConfig(path)
|
||||
if err == nil {
|
||||
@@ -223,7 +128,7 @@ func TestLoadConfigMalformedSecretLineHidesValue(t *testing.T) {
|
||||
}
|
||||
|
||||
message := err.Error()
|
||||
for _, part := range []string{secret, "AAHfake", "secret-token-value", "123456"} {
|
||||
for _, part := range []string{secret, "AQVNfake", "secret-api-key-value"} {
|
||||
if strings.Contains(message, part) {
|
||||
t.Fatalf("значение ключа доступа уехало в отказ: %v", err)
|
||||
}
|
||||
@@ -232,7 +137,7 @@ func TestLoadConfigMalformedSecretLineHidesValue(t *testing.T) {
|
||||
if !strings.Contains(message, "строке 3") {
|
||||
t.Fatalf("номер строки не назван, чинить нечего: %v", err)
|
||||
}
|
||||
if !strings.Contains(message, "bot_token") {
|
||||
if !strings.Contains(message, "speech_kit_api_key") {
|
||||
t.Fatalf("ключ не назван, чинить нечего: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -256,9 +161,9 @@ func TestLoadConfigTypeMismatchKeepsDiagnostics(t *testing.T) {
|
||||
// уязвимая: именно в ней будущая правка легче всего протащит текст библиотеки
|
||||
// обратно.
|
||||
func TestLoadConfigMalformedBeforeAnyKeyHidesValue(t *testing.T) {
|
||||
const secret = "123456:AAHsecret-token-value"
|
||||
const secret = "AQVNsecret-api-key-value"
|
||||
// У секции не закрыта скобка: разбор оборвётся, не назвав ни одного ключа.
|
||||
path := writeConfig(t, "[telegram\nenabled = true\nbot_token = \""+secret+"\"\n")
|
||||
path := writeConfig(t, "[yandex\nfolder_id = \"b1g\"\nspeech_kit_api_key = \""+secret+"\"\n")
|
||||
|
||||
_, err := LoadConfig(path)
|
||||
if err == nil {
|
||||
@@ -266,7 +171,7 @@ func TestLoadConfigMalformedBeforeAnyKeyHidesValue(t *testing.T) {
|
||||
}
|
||||
|
||||
message := err.Error()
|
||||
for _, part := range []string{secret, "AAHsecret", "secret-token-value"} {
|
||||
for _, part := range []string{secret, "AQVNsecret", "secret-api-key-value"} {
|
||||
if strings.Contains(message, part) {
|
||||
t.Fatalf("значение ключа доступа уехало в отказ: %v", err)
|
||||
}
|
||||
@@ -284,8 +189,8 @@ workers = 7
|
||||
own_work_limit_minutes = 90
|
||||
foreign_work_limit_minutes = 720
|
||||
|
||||
[telegram]
|
||||
enabled = false
|
||||
[storage]
|
||||
data_dir = "data"
|
||||
`)
|
||||
|
||||
cfg, err := LoadConfig(path)
|
||||
@@ -309,7 +214,7 @@ enabled = false
|
||||
// Умолчания есть у всех трёх чисел: файл без секции конвейера годен, и сервис
|
||||
// поднимается с рабочими значениями.
|
||||
func TestPipelineSettingsHaveDefaults(t *testing.T) {
|
||||
path := writeConfig(t, "[telegram]\nenabled = false\n")
|
||||
path := writeConfig(t, "[storage]\ndata_dir = \"data\"\n")
|
||||
|
||||
cfg, err := LoadConfig(path)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user