7ce4c2c79f
Реализация варианта B из аудита защиты знаний. Прямой garble всего bj-server ломает html/template (он обращается к полям структур по имени, garble их переименовывает). Решение — селективная обфускация: GOGARBLE обфусцирует ТОЛЬКО пакет с секретами, шаблоны и обработчики не затрагиваются. 95 SQL-запросов к DEPO/SPECTRE/QUIK и трансферным таблицам FTV_M2M вынесены из lkgateway в отдельный пакет internal/lkgateway/fsql как переменные (именно var, не const: const компилятор вставил бы обратно в необфусцируемый пакет, и шифрование бы не сработало — проверено). Сами обработчики, типы и параметры sql.Named остались на месте; пакет держит только строки запросов. build-release.sh собирает bj-server с `GOGARBLE=.../fsql garble -literals` (нужен отдельный SDK go1.26 — модуль-кэшный тулчейн garble не подходит); если garble/go1.26 не найдены — обычная сборка с предупреждением. Проверено на реальном garble-бинаре (запускается, /admin и страницы на структурах работают): рецепт чтения чужих систем из бинаря исчез — OD_RESTS 13→0, OD_WIRING 5→0, OD_TURNS 1→0, OD_BALANCES 3→0, D_B_CONTRACTS 1→0, OD_CLIENTS_PORTS 10→0, DepoLimits/MoneyLimits →0. Остаток FTV_M2M — упоминания имён таблиц в операторских embed-шаблонах и логах, не сам рецепт. Стенд собран обычной сборкой (без garble) — трассировки остаются читаемыми. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
116 lines
4.2 KiB
Go
116 lines
4.2 KiB
Go
package lkgateway
|
||
|
||
// lkclient.go — как один и тот же клиент называется в разных системах.
|
||
//
|
||
// В учётных записях кабинета клиент задан номером договора, как в DEPO: «БР-1545», «БРИИС-514».
|
||
// В экспорте QUIK тот же клиент — код латиницей без дефиса: BR1545, BRIIS514; внебиржевой
|
||
// раздел — с суффиксом OUT. В SPECTRE договор — строка D_B_CONTRACTS.NAME вида «БР-1545 ФИО».
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway/fsql"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// lkContractNumber — номер договора без ФИО: «БР-1545 Иванов Иван» → «БР-1545».
|
||
func lkContractNumber(contract string) string {
|
||
c := strings.TrimSpace(contract)
|
||
if i := strings.IndexByte(c, ' '); i > 0 {
|
||
c = c[:i]
|
||
}
|
||
return strings.ToUpper(c)
|
||
}
|
||
|
||
// lkQuikCodes — коды клиента в QUIK по номеру договора: основной и внебиржевой раздел.
|
||
func lkQuikCodes(contract string) []string {
|
||
num := lkContractNumber(contract)
|
||
if num == "" {
|
||
return nil
|
||
}
|
||
repl := strings.NewReplacer("-", "", "А", "A", "Б", "B", "В", "V", "Д", "D", "И", "I", "С", "S", "Р", "R", "У", "U", "Т", "T", "К", "K", "М", "M", "Н", "N", "О", "O", "Е", "E", "П", "P", "Ф", "F", "Ц", "C")
|
||
code := repl.Replace(num)
|
||
return []string{code, code + "OUT"}
|
||
}
|
||
|
||
// spectreContract — договор клиента в SPECTRE.
|
||
type spectreContract struct {
|
||
DOC int64
|
||
Investor int64
|
||
Name string
|
||
Open time.Time
|
||
Closed bool
|
||
}
|
||
|
||
var spectreContractCache = struct {
|
||
mu sync.Mutex
|
||
m map[string]spectreContract
|
||
at map[string]time.Time
|
||
}{m: map[string]spectreContract{}, at: map[string]time.Time{}}
|
||
|
||
// spectreOpen — подключение к копии SPECTRE тем же логином, что и к DEPO.
|
||
func spectreOpen(s FancyMSSQLSettings) (*sql.DB, error) {
|
||
db := strings.TrimSpace(s.SpectreDB())
|
||
return fancyOpen(s, db)
|
||
}
|
||
|
||
// SpectreDB — база SPECTRE рядом с DEPO: по умолчанию BJ_SPECTRE_DATA.
|
||
func (s FancyMSSQLSettings) SpectreDB() string {
|
||
if v := strings.TrimSpace(s.SpectreDatabase); v != "" {
|
||
return v
|
||
}
|
||
return "BJ_SPECTRE_DATA"
|
||
}
|
||
|
||
// spectreContractFor — договор SPECTRE по номеру договора клиента (кэш 10 минут).
|
||
func spectreContractFor(ctx context.Context, s FancyMSSQLSettings, contract string) (spectreContract, bool) {
|
||
num := lkContractNumber(contract)
|
||
if num == "" || strings.TrimSpace(s.Server) == "" {
|
||
return spectreContract{}, false
|
||
}
|
||
spectreContractCache.mu.Lock()
|
||
if c, ok := spectreContractCache.m[num]; ok && time.Since(spectreContractCache.at[num]) < 10*time.Minute {
|
||
spectreContractCache.mu.Unlock()
|
||
return c, c.DOC != 0
|
||
}
|
||
spectreContractCache.mu.Unlock()
|
||
conn, err := spectreOpen(s)
|
||
if err != nil {
|
||
return spectreContract{}, false
|
||
}
|
||
defer conn.Close()
|
||
cctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||
defer cancel()
|
||
var c spectreContract
|
||
var open, closed sql.NullTime
|
||
var state sql.NullInt64
|
||
err = conn.QueryRowContext(cctx, fsql.Cl01, sql.Named("n", num)).
|
||
Scan(&c.DOC, &c.Investor, &c.Name, &open, &closed, &state)
|
||
if err == nil {
|
||
c.Name = strings.TrimSpace(c.Name)
|
||
if open.Valid {
|
||
c.Open = open.Time
|
||
}
|
||
c.Closed = closed.Valid && closed.Time.Year() < 9000 && closed.Time.Before(time.Now())
|
||
}
|
||
spectreContractCache.mu.Lock()
|
||
spectreContractCache.m[num], spectreContractCache.at[num] = c, time.Now()
|
||
spectreContractCache.mu.Unlock()
|
||
return c, err == nil && c.DOC != 0
|
||
}
|
||
|
||
// lkClientName — ФИО клиента для показа: из учётной записи, иначе из номера договора,
|
||
// где учётная система хранит номер вместе с ФИО («БР-719 Аминов Вячеслав Маркович»).
|
||
func lkClientName(u LKUser) string {
|
||
if n := strings.TrimSpace(u.FullName); n != "" {
|
||
return n
|
||
}
|
||
c := strings.TrimSpace(u.Contract)
|
||
if i := strings.IndexByte(c, ' '); i > 0 {
|
||
return strings.TrimSpace(c[i:])
|
||
}
|
||
return ""
|
||
}
|