feat(bj): модуль ЛК на PostgreSQL, обмен с DEPO, боевой ЛК с имитатором НРД, QUIK, НПР
- bj_exchange и «кран» Fancy убраны: данные модуля ЛК в PostgreSQL (схема lk) - трансферные таблицы FTV_M2M: создание при установке, события, справочник участников - сверка с DEPO: счёт, ФИО, дата рождения, ИИС по договору, квал-инвестор из опции DEPO - боевой ЛК ЕСИА Финанс при тестовом ИШ: журнал, отказ через DEPO, статусы в ЛК - онлайн-лицензирование в bj, план тестирования M2M для контрагента (интерактивный HTML) - модули QUIK МНП, экспорт QUIK, НПР/УДС; документы и тестовые материалы Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,79 +1,153 @@
|
||||
package lkgateway
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/license"
|
||||
)
|
||||
|
||||
// DefaultLicensePublicKey — публичный ключ лицензий, зашитый в релиз.
|
||||
// Пустой в исходниках; подставляется при официальной сборке. Если задан в
|
||||
// настройках (LicenseSettings.PublicKey) — приоритет у настроек.
|
||||
var DefaultLicensePublicKey = ""
|
||||
// DefaultLicensePublicKey — открытый ключ проверки лицензий: пара к ключу подписи сервера
|
||||
// лицензий lic.zetit.ru. Зашит в bj и в настройках не меняется, поэтому ключ, подписанный
|
||||
// чем-то другим, не принимается. var — только для подмены в тестах.
|
||||
var DefaultLicensePublicKey = "UuvEYFxxzOFk59FjCQTdxKuzkNAJpJyWTuUdlCYfsdI="
|
||||
|
||||
// LicenseStatus — сводка состояния лицензии для UI и гейтов.
|
||||
type LicenseStatus struct {
|
||||
Present bool // ключ введён
|
||||
Valid bool // подпись верна и срок не истёк
|
||||
Present bool // ключ введён
|
||||
Valid bool // подпись верна, срок не истёк, ключ этой установки
|
||||
Online bool // выдан сервером лицензий под эту установку и продлевается автоматически
|
||||
LicenseID string
|
||||
Tenant string
|
||||
Plan string
|
||||
ExpiresAt time.Time
|
||||
ContractUntil time.Time
|
||||
DaysLeft int
|
||||
AllowsUpdates bool
|
||||
Features []string // признаки (модули), разрешённые ключом
|
||||
Message string
|
||||
}
|
||||
|
||||
// licensingEnabled — включено ли лицензирование (есть публичный ключ для
|
||||
// проверки). Если ключа нет вовсе — продукт в открытом режиме, гейты не
|
||||
// действуют (удобно для разработки и бесплатной редакции).
|
||||
func licensingEnabled(rc *RuntimeConfig) bool {
|
||||
return rc.Snapshot().License.PublicKey != "" || DefaultLicensePublicKey != ""
|
||||
// LicenseFeatureView — признак лицензии для отображения.
|
||||
type LicenseFeatureView struct {
|
||||
Key, Title string
|
||||
On bool
|
||||
}
|
||||
|
||||
// knownFeatures — известные признаки (модули) и их названия.
|
||||
var knownFeatures = []struct{ Key, Title string }{
|
||||
{"updates", "Обновления через артефакторию"},
|
||||
{"web-cabinet", "Личный кабинет клиента (bj-lk)"},
|
||||
{"quik-mnp", "QUIK: модуль неторговых поручений (цикл M2M)"},
|
||||
{"quik-export", "QUIK: экспорт биржевой информации"},
|
||||
{"npr", "Риск-монитор НПР / УДС"},
|
||||
}
|
||||
|
||||
func licenseFeatureViews(rc *RuntimeConfig) []LicenseFeatureView {
|
||||
out := make([]LicenseFeatureView, 0, len(knownFeatures))
|
||||
for _, f := range knownFeatures {
|
||||
out = append(out, LicenseFeatureView{Key: f.Key, Title: f.Title, On: moduleAllowed(rc, f.Key)})
|
||||
}
|
||||
st := licenseStatus(rc)
|
||||
for _, f := range st.Features {
|
||||
known := false
|
||||
for _, k := range knownFeatures {
|
||||
if k.Key == f {
|
||||
known = true
|
||||
}
|
||||
}
|
||||
if !known {
|
||||
out = append(out, LicenseFeatureView{Key: f, Title: f, On: st.Valid})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func modulesAllowed(rc *RuntimeConfig) map[string]bool {
|
||||
m := map[string]bool{}
|
||||
for _, f := range knownFeatures {
|
||||
m[f.Key] = moduleAllowed(rc, f.Key)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// moduleAllowed — разрешён ли модуль (лицензионный признак). План enterprise — все модули.
|
||||
func moduleAllowed(rc *RuntimeConfig, feature string) bool {
|
||||
st := licenseStatus(rc)
|
||||
if !st.Valid {
|
||||
return false
|
||||
}
|
||||
if st.Plan == string(license.PlanEnterprise) {
|
||||
return true
|
||||
}
|
||||
for _, f := range st.Features {
|
||||
if f == feature {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// licensingEnabled — лицензирование действует всегда: ключ проверки зашит в bj.
|
||||
func licensingEnabled(_ *RuntimeConfig) bool { return DefaultLicensePublicKey != "" }
|
||||
|
||||
// verifyLicenseToken — подпись ключа зашитым открытым ключом (срок не проверяется).
|
||||
func verifyLicenseToken(key string) (*license.License, error) {
|
||||
pub, err := license.ParsePublicKey(DefaultLicensePublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tok, err := license.DecodeToken(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return license.Verify(tok, pub)
|
||||
}
|
||||
|
||||
// licenseStatus разбирает и проверяет лицензию из настроек.
|
||||
func licenseStatus(rc *RuntimeConfig) LicenseStatus {
|
||||
s := rc.Snapshot().License
|
||||
st := LicenseStatus{}
|
||||
if s.Key == "" {
|
||||
st.Message = "лицензионный ключ не введён"
|
||||
if strings.TrimSpace(s.Key) == "" {
|
||||
st.Message = "лицензия не активирована"
|
||||
return st
|
||||
}
|
||||
st.Present = true
|
||||
|
||||
pubB64 := s.PublicKey
|
||||
if pubB64 == "" {
|
||||
pubB64 = DefaultLicensePublicKey
|
||||
}
|
||||
if pubB64 == "" {
|
||||
st.Message = "нет публичного ключа для проверки лицензии"
|
||||
return st
|
||||
}
|
||||
pub, err := license.ParsePublicKey(pubB64)
|
||||
lic, err := verifyLicenseToken(s.Key)
|
||||
if err != nil {
|
||||
st.Message = "неверный публичный ключ: " + err.Error()
|
||||
return st
|
||||
}
|
||||
tok, err := license.DecodeToken(s.Key)
|
||||
if err != nil {
|
||||
st.Message = "неверный формат ключа: " + err.Error()
|
||||
return st
|
||||
}
|
||||
lic, err := license.Verify(tok, pub)
|
||||
if err != nil {
|
||||
st.Message = "подпись лицензии недействительна"
|
||||
st.Message = "ключ недействителен: подпись не сходится или ключ повреждён"
|
||||
return st
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
st.LicenseID = lic.ID
|
||||
st.Tenant = lic.Tenant
|
||||
st.Plan = string(lic.Plan)
|
||||
st.ExpiresAt = lic.ExpiresAt
|
||||
st.ContractUntil = lic.ContractUntil
|
||||
st.DaysLeft = lic.DaysLeft(now)
|
||||
st.AllowsUpdates = lic.AllowsUpdates()
|
||||
st.Features = lic.Features
|
||||
st.Online = lic.InstallID != ""
|
||||
if lic.Product != "" && lic.Product != "bj-server" {
|
||||
st.Message = "ключ выпущен для другого продукта (" + lic.Product + ")"
|
||||
return st
|
||||
}
|
||||
if st.Online && !strings.EqualFold(lic.InstallID, s.InstallID) {
|
||||
st.Message = "ключ выдан другой установке: активируйте эту установку кодом"
|
||||
return st
|
||||
}
|
||||
if err := lic.Valid(now); err != nil {
|
||||
st.Message = err.Error()
|
||||
return st
|
||||
}
|
||||
st.Valid = true
|
||||
st.Message = "активна до " + lic.ExpiresAt.Format("02.01.2006")
|
||||
if st.Online {
|
||||
st.Message = "активна до " + lic.ExpiresAt.Format("02.01.2006") + ", продлевается автоматически"
|
||||
if !lic.ContractUntil.IsZero() {
|
||||
st.Message += "; договор до " + lic.ContractUntil.Format("02.01.2006")
|
||||
}
|
||||
} else {
|
||||
st.Message = "активна до " + lic.ExpiresAt.Format("02.01.2006") + " (офлайн-ключ)"
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user