diff --git a/internal/lkgateway/depo_execas_test.go b/internal/lkgateway/depo_execas_test.go new file mode 100644 index 0000000..5250b13 --- /dev/null +++ b/internal/lkgateway/depo_execas_test.go @@ -0,0 +1,25 @@ +package lkgateway + +import ( + "strings" + "testing" +) + +// FTV_M2M_JOB ставится с WITH EXECUTE AS 'SYSDBA' — обработка от SYSDBA и при вызове из bj; остальные — как есть. +func TestDepoProcInstallBodyExecAs(t *testing.T) { + var f FancyMSSQLSettings + job := depoProcInstallBody(f, "FTV_M2M_JOB") + if !strings.Contains(job, "with execute as 'SYSDBA'\nas\n") { + t.Fatalf("JOB без EXECUTE AS:\n%s", job[:200]) + } + if strings.Index(job, "with execute as") > strings.Index(job, "set nocount on") { + t.Fatal("EXECUTE AS не перед телом процедуры") + } + if depoProcInstallBody(f, "FTV_M2M_PROCESS_CREATE") != depoProcEffective(f, "FTV_M2M_PROCESS_CREATE") { + t.Fatal("CREATE изменён") + } + f.DepoProcOverrides = map[string]string{"FTV_M2M_JOB": "CREATE OR ALTER procedure dbo.FTV_M2M_JOB\nWITH EXECUTE AS OWNER\nas\nselect 1"} + if got := depoProcInstallBody(f, "FTV_M2M_JOB"); strings.Count(strings.ToLower(got), "execute as") != 1 { + t.Fatalf("второй EXECUTE AS к правке под клиента: %s", got) + } +} diff --git a/internal/lkgateway/depo_procs.go b/internal/lkgateway/depo_procs.go index aab1cc6..876e786 100644 --- a/internal/lkgateway/depo_procs.go +++ b/internal/lkgateway/depo_procs.go @@ -78,6 +78,33 @@ func depoProcEffective(f FancyMSSQLSettings, name string) string { return embeddedProcBody(name) } +// depoProcInstallBody — текст процедуры, который bj устанавливает: depoProcEffective, а у FTV_M2M_JOB — +// с WITH EXECUTE AS '<пользователь процедур>' (обычно SYSDBA). Процедуры Фонтвьель переходят на SYSDBA только +// при вызове от dbo (джоба SQL Agent); вызванные bj, они работали правами bj_user, и Fancy отказывал: +// «Создание документа запрещено для данного пользователя» (FU_HAVE_RIGHT пропускает только SYSDBA%; +// 22.10.2026, первый боевой входящий перевод на 9). С EXECUTE AS на JOB обработка идёт от SYSDBA при любом +// способе запуска — джобой SQL Agent или из bj. +func depoProcInstallBody(f FancyMSSQLSettings, name string) string { + body := depoProcEffective(f, name) + if name != "FTV_M2M_JOB" || withExecAsRe.MatchString(body) { + return body + } + user := depoProcExecAsUser(f) + if !depoLoginRe.MatchString(user) { + return body + } + loc := procAsLineRe.FindStringIndex(body) + if loc == nil { + return body + } + return body[:loc[0]] + "with execute as '" + user + "'\n" + body[loc[0]:] +} + +var ( + withExecAsRe = regexp.MustCompile(`(?i)\bwith\s+execute\s+as\b`) + procAsLineRe = regexp.MustCompile(`(?im)^[ \t]*as[ \t]*\r?$`) // строка «as» перед телом процедуры +) + // depoProcOrderText — порядок установки коротко: WIRING→OPERATION→UPDATE_HANDBOOK→… func depoProcOrderText(sep string) string { var parts []string @@ -103,7 +130,7 @@ func depoProcsHash(f FancyMSSQLSettings) string { } h.Write([]byte("grant db_datawriter\n")) // 22.10.2026: роль записи логину bj — новый отпечаток, плашка на установках for _, p := range depoProcInstallOrder { - h.Write([]byte(p.Name + "\n" + strings.TrimSpace(depoProcEffective(f, p.Name)) + "\n")) + h.Write([]byte(p.Name + "\n" + strings.TrimSpace(depoProcInstallBody(f, p.Name)) + "\n")) } return hex.EncodeToString(h.Sum(nil))[:16] } @@ -141,7 +168,7 @@ func depoProcsScriptFor(f FancyMSSQLSettings, db, login string) string { fmt.Fprintf(&b, "/* ===== структура: %s ===== */\n%s;\nGO\n\n", fx.Name, fx.SQL) } for _, p := range depoProcInstallOrder { - body := depoProcEffective(f, p.Name) + body := depoProcInstallBody(f, p.Name) if strings.TrimSpace(body) == "" { continue } @@ -173,7 +200,7 @@ func depoProcsInstallInto(ctx context.Context, adm, f FancyMSSQLSettings, dbName } } for _, p := range depoProcInstallOrder { - body := depoProcEffective(f, p.Name) + body := depoProcInstallBody(f, p.Name) if strings.TrimSpace(body) == "" { return fmt.Errorf("пустой текст процедуры %s", p.Name) }