627dbca6af
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
174 lines
6.1 KiB
Go
174 lines
6.1 KiB
Go
package lkgateway
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/license"
|
|
)
|
|
|
|
// DefaultLicensePublicKey — открытый ключ проверки лицензий: пара к ключу подписи сервера
|
|
// лицензий lic.zetit.ru. Зашит в bj и в настройках не меняется, поэтому ключ, подписанный
|
|
// чем-то другим, не принимается. var — только для подмены в тестах.
|
|
var DefaultLicensePublicKey = license.DefaultPublicKey
|
|
|
|
// LicenseStatus — сводка состояния лицензии для UI и гейтов.
|
|
type LicenseStatus struct {
|
|
Present bool // ключ введён
|
|
Valid bool // подпись верна, срок не истёк, ключ этой установки
|
|
Online bool // выдан сервером лицензий под эту установку и продлевается автоматически
|
|
LicenseID string
|
|
Tenant string
|
|
Plan string
|
|
ExpiresAt time.Time
|
|
ContractUntil time.Time
|
|
DaysLeft int
|
|
AllowsUpdates bool
|
|
Features []string // признаки (модули), разрешённые ключом
|
|
DevUntil time.Time // режим разработки до (выдаёт сервер лицензий; нулевое — выключен)
|
|
Message string
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// devModeForced — режим разработки в тестах пакета.
|
|
var devModeForced bool
|
|
|
|
// devMode — режим разработки: сервер лицензий временно открыл разработчикам bj тестовые разделы и
|
|
// служебные инструменты на этой установке. Без него у клиента их нет (адреса отвечают 404).
|
|
func devMode(rc *RuntimeConfig) bool {
|
|
if devModeForced {
|
|
return true
|
|
}
|
|
if rc == nil {
|
|
return false
|
|
}
|
|
st := licenseStatus(rc)
|
|
return st.Valid && time.Now().Before(st.DevUntil)
|
|
}
|
|
|
|
// 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 strings.TrimSpace(s.Key) == "" {
|
|
st.Message = "лицензия не активирована"
|
|
return st
|
|
}
|
|
st.Present = true
|
|
lic, err := verifyLicenseToken(s.Key)
|
|
if err != nil {
|
|
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
|
|
if lic.DevUntil != nil {
|
|
st.DevUntil = *lic.DevUntil
|
|
}
|
|
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
|
|
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
|
|
}
|