DEPO: процедуры с EXECUTE AS — проверка пользователя SYSDBA, понятная ошибка

Процедуры FTV_M2M_PROCESS_* содержат EXECUTE AS USER=SYSDBA; SQL Server требует
этого пользователя при CREATE (FTV_M2M_JOB без EXECUTE AS создаётся, а 4 PROCESS_* — нет).
- «Проверить возможности» проверяет наличие пользователя EXECUTE AS (из текста процедур)
  и блокирует установку с понятным сообщением, если его нет.
- Ошибка установки сохраняется в статус (не мелькает флешем) с подсказкой про SYSDBA.
- Подсказка добавлена и в мастере при «Подготовить».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
fontvielle
2026-10-20 14:21:11 +03:00
parent 8a298ae074
commit a3abcd5412
2 changed files with 39 additions and 6 deletions
+38 -5
View File
@@ -156,13 +156,15 @@ func depoProcsPreflight(ctx context.Context, f FancyMSSQLSettings, adminUser, ad
} }
missing := depoMissing(ctx, db, depoBj2depoProcs, "P") missing := depoMissing(ctx, db, depoBj2depoProcs, "P")
login := depoSetupLogin(f) login := depoSetupLogin(f)
var canCreate, sysadmin, bjUser int execAs := depoProcExecAsUser(f) // пользователь для EXECUTE AS в теле процедур (обычно SYSDBA)
var canCreate, sysadmin, bjUser, execAsOK int
_ = db.QueryRowContext(ctx, _ = db.QueryRowContext(ctx,
`SELECT CAST(ISNULL(HAS_PERMS_BY_NAME(NULL,'DATABASE','CREATE PROCEDURE'),0) AS int), `SELECT CAST(ISNULL(HAS_PERMS_BY_NAME(NULL,'DATABASE','CREATE PROCEDURE'),0) AS int),
CAST(ISNULL(IS_SRVROLEMEMBER('sysadmin'),0) AS int), CAST(ISNULL(IS_SRVROLEMEMBER('sysadmin'),0) AS int),
CASE WHEN DATABASE_PRINCIPAL_ID(@u) IS NULL THEN 0 ELSE 1 END`, CASE WHEN DATABASE_PRINCIPAL_ID(@u) IS NULL THEN 0 ELSE 1 END,
sql.Named("u", login)).Scan(&canCreate, &sysadmin, &bjUser) CASE WHEN DATABASE_PRINCIPAL_ID(@e) IS NULL THEN 0 ELSE 1 END`,
canApply = canCreate == 1 || sysadmin == 1 sql.Named("u", login), sql.Named("e", execAs)).Scan(&canCreate, &sysadmin, &bjUser, &execAsOK)
canApply = (canCreate == 1 || sysadmin == 1) && execAsOK == 1
var b strings.Builder var b strings.Builder
fmt.Fprintf(&b, "Сервер %s, база %s, логин %s.\n", f.Server, f.DepoDB(), conn.User) fmt.Fprintf(&b, "Сервер %s, база %s, логин %s.\n", f.Server, f.DepoDB(), conn.User)
@@ -181,10 +183,37 @@ func depoProcsPreflight(ctx context.Context, f FancyMSSQLSettings, adminUser, ad
} else { } else {
fmt.Fprintf(&b, "Логин %s в базе: нет — сначала подготовьте базу (логин/таблицы), GRANT будет пропущен.\n", login) fmt.Fprintf(&b, "Логин %s в базе: нет — сначала подготовьте базу (логин/таблицы), GRANT будет пропущен.\n", login)
} }
if execAsOK == 1 {
fmt.Fprintf(&b, "Пользователь %s (для EXECUTE AS в процедурах): есть.\n", execAs)
} else {
fmt.Fprintf(&b, "Пользователь %s (для EXECUTE AS в процедурах): НЕТ — процедуры FTV_M2M_PROCESS_* используют EXECUTE AS USER='%s' и не создадутся без него. Создайте этого пользователя в базе (как в рабочей базе Fancy) или замените имя в «Тексты процедур».\n", execAs, execAs)
}
b.WriteString("Будет выполнено: CREATE OR ALTER 5 процедур (WIRING→OPERATION→EVENTS→CREATE→JOB) + GRANT EXECUTE на FTV_M2M_JOB.") b.WriteString("Будет выполнено: CREATE OR ALTER 5 процедур (WIRING→OPERATION→EVENTS→CREATE→JOB) + GRANT EXECUTE на FTV_M2M_JOB.")
return b.String(), canApply, nil return b.String(), canApply, nil
} }
// depoProcExecAsUser — пользователь, под которым процедуры выполняют EXECUTE AS (из их текста,
// строка «execute as user = '...'»). По умолчанию SYSDBA.
func depoProcExecAsUser(f FancyMSSQLSettings) string {
body := depoProcEffective(f, "FTV_M2M_PROCESS_WIRING")
if m := execAsUserRe.FindStringSubmatch(body); len(m) == 2 {
return m[1]
}
return "SYSDBA"
}
var execAsUserRe = regexp.MustCompile(`(?i)execute\s+as\s+user\s*=\s*'([^']+)'`)
// depoProcErrHint — подсказка по типичной причине отказа CREATE процедур.
func depoProcErrHint(msg string) string {
l := strings.ToLower(msg)
if strings.Contains(l, "sysdba") || strings.Contains(l, "principal") || strings.Contains(l, "15517") ||
strings.Contains(l, "execute as") || strings.Contains(l, "не удается олицетворить") || strings.Contains(l, "олицетвор") {
return " Процедуры FTV_M2M_PROCESS_* используют EXECUTE AS USER='SYSDBA' — в базе DEPO должен быть пользователь SYSDBA (как в рабочей базе Fancy). Создайте его или замените имя пользователя в «Тексты процедур» и повторите."
}
return ""
}
// depoProcsInstall — установить процедуры логином администратора. Сначала preflight; при отсутствии // depoProcsInstall — установить процедуры логином администратора. Сначала preflight; при отсутствии
// прав — отказ без изменений. Возвращает итоговое сообщение. // прав — отказ без изменений. Возвращает итоговое сообщение.
func depoProcsInstall(ctx context.Context, f FancyMSSQLSettings, adminUser, adminPass string) (string, error) { func depoProcsInstall(ctx context.Context, f FancyMSSQLSettings, adminUser, adminPass string) (string, error) {
@@ -257,7 +286,11 @@ func (h *setupHandlers) installFancyDepoProcs(w http.ResponseWriter, r *http.Req
msg, err := depoProcsInstall(ctx, s, r.FormValue("admin_user"), r.FormValue("admin_password")) msg, err := depoProcsInstall(ctx, s, r.FormValue("admin_user"), r.FormValue("admin_password"))
if err != nil { if err != nil {
log.Printf("depo-procs: установка не удалась: %v", err) log.Printf("depo-procs: установка не удалась: %v", err)
setupFlash(w, r, "Процедуры DEPO: не установлены — "+err.Error()) txt := "установка не удалась: " + err.Error() + depoProcErrHint(err.Error())
s2 := h.rc.Snapshot().Fancy // сохранить ошибку, чтобы не мелькала во флеше
s2.DepoProcCheck, s2.DepoProcCheckOK, s2.DepoProcCheckAt = txt, false, time.Now()
_ = h.rc.UpdateFancy(s2)
setupFlash(w, r, "Процедуры DEPO: "+txt)
return return
} }
pmsg, pok := depoProcCheck(ctx, h.rc) pmsg, pok := depoProcCheck(ctx, h.rc)
+1 -1
View File
@@ -418,7 +418,7 @@ func (h *setupHandlers) depoSetupDo(ctx context.Context, r *http.Request, step d
adm := s adm := s
adm.User, adm.Password = au, r.FormValue("admin_password") adm.User, adm.Password = au, r.FormValue("admin_password")
if err := depoProcsInstallInto(ctx, adm, s, db, s.User); err != nil { if err := depoProcsInstallInto(ctx, adm, s, db, s.User); err != nil {
return fail("процедуры обработки не установлены — " + err.Error()) return fail("процедуры обработки не установлены — " + err.Error() + depoProcErrHint(err.Error()))
} }
step("procs", "ok", "") step("procs", "ok", "")
} }