Скрипт создания логина DEPO: пароль из поля мастера подставляется в скрипт (передаётся только в теле POST, не сохраняется, кавычки экранируются)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -10,26 +10,32 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// depoLoginScript — скрипт создания логина bj. pg — PostgreSQL; db — база DEPO; login — имя логина.
|
// depoLoginScript — скрипт создания логина bj. pg — PostgreSQL; db — база DEPO; login — имя логина;
|
||||||
func depoLoginScript(pg bool, db, login string) string {
|
// pass — пароль из поля мастера (пусто — заглушка, администратор впишет сам). Пароль только вставляется
|
||||||
|
// в текст скрипта, нигде не сохраняется.
|
||||||
|
func depoLoginScript(pg bool, db, login, pass string) string {
|
||||||
if !validDBName(db) {
|
if !validDBName(db) {
|
||||||
db = depoDefaultDatabase
|
db = depoDefaultDatabase
|
||||||
}
|
}
|
||||||
if !depoLoginRe.MatchString(login) {
|
if !depoLoginRe.MatchString(login) {
|
||||||
login = "bj_user"
|
login = "bj_user"
|
||||||
}
|
}
|
||||||
|
pw := "ЗАМЕНИТЕ_НА_ПАРОЛЬ"
|
||||||
|
if pass != "" {
|
||||||
|
pw = strings.ReplaceAll(pass, "'", "''") // строковый литерал SQL: одинарная кавычка удваивается
|
||||||
|
}
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
if pg {
|
if pg {
|
||||||
fmt.Fprintf(&b, `-- Bridge&Join: роль (логин) для bj в базе DEPO. PostgreSQL 10 и новее.
|
fmt.Fprintf(&b, `-- Bridge&Join: роль (логин) для bj в базе DEPO. PostgreSQL 10 и новее.
|
||||||
-- Выполнить суперпользователем или владельцем базы, например:
|
-- Выполнить суперпользователем или владельцем базы, например:
|
||||||
-- psql -h <сервер> -U postgres -f bj-depo-login-postgres.sql
|
-- psql -h <сервер> -U postgres -f bj-depo-login-postgres.sql
|
||||||
-- Пароль замените на свой и передайте его тому, кто настраивает bj (в мастере, шаг «Подключение»).
|
-- Пароль — из поля мастера; если там пусто, замените ЗАМЕНИТЕ_НА_ПАРОЛЬ на свой. Скрипт с паролем не храните.
|
||||||
-- Права: только подключение и чтение учётных таблиц DEPO. Запись — только в таблицы обмена FTV_M2M*,
|
-- Права: только подключение и чтение учётных таблиц DEPO. Запись — только в таблицы обмена FTV_M2M*,
|
||||||
-- её выдаёт скрипт таблиц на следующем шаге мастера.
|
-- её выдаёт скрипт таблиц на следующем шаге мастера.
|
||||||
|
|
||||||
DO $$ BEGIN
|
DO $$ BEGIN
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '%[2]s') THEN
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '%[2]s') THEN
|
||||||
CREATE ROLE "%[2]s" LOGIN PASSWORD 'ЗАМЕНИТЕ_НА_ПАРОЛЬ';
|
CREATE ROLE "%[2]s" LOGIN PASSWORD '%[3]s';
|
||||||
END IF;
|
END IF;
|
||||||
END $$;
|
END $$;
|
||||||
|
|
||||||
@@ -39,12 +45,12 @@ GRANT CONNECT ON DATABASE "%[1]s" TO "%[2]s";
|
|||||||
GRANT USAGE ON SCHEMA public TO "%[2]s";
|
GRANT USAGE ON SCHEMA public TO "%[2]s";
|
||||||
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "%[2]s";
|
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "%[2]s";
|
||||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO "%[2]s";
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO "%[2]s";
|
||||||
`, db, login)
|
`, db, login, pw)
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&b, `-- Bridge&Join: логин для bj в базе DEPO. Microsoft SQL Server.
|
fmt.Fprintf(&b, `-- Bridge&Join: логин для bj в базе DEPO. Microsoft SQL Server.
|
||||||
-- Выполнить в SSMS или sqlcmd под логином с правами sysadmin (или securityadmin + db_owner в базе %[1]s).
|
-- Выполнить в SSMS или sqlcmd под логином с правами sysadmin (или securityadmin + db_owner в базе %[1]s).
|
||||||
-- Пароль замените на свой и передайте его тому, кто настраивает bj (в мастере, шаг «Подключение»).
|
-- Пароль — из поля мастера; если там пусто, замените ЗАМЕНИТЕ_НА_ПАРОЛЬ на свой. Скрипт с паролем не храните.
|
||||||
-- Права: только чтение учётных таблиц DEPO (db_datareader). Запись — только в таблицы обмена FTV_M2M*,
|
-- Права: только чтение учётных таблиц DEPO (db_datareader). Запись — только в таблицы обмена FTV_M2M*,
|
||||||
-- её выдаёт скрипт таблиц на следующем шаге мастера. Скрипт можно запускать повторно.
|
-- её выдаёт скрипт таблиц на следующем шаге мастера. Скрипт можно запускать повторно.
|
||||||
|
|
||||||
@@ -52,7 +58,7 @@ USE [master];
|
|||||||
GO
|
GO
|
||||||
|
|
||||||
IF SUSER_ID(N'%[2]s') IS NULL
|
IF SUSER_ID(N'%[2]s') IS NULL
|
||||||
CREATE LOGIN [%[2]s] WITH PASSWORD = N'ЗАМЕНИТЕ_НА_ПАРОЛЬ', CHECK_POLICY = ON, DEFAULT_DATABASE = [%[1]s];
|
CREATE LOGIN [%[2]s] WITH PASSWORD = N'%[3]s', CHECK_POLICY = ON, DEFAULT_DATABASE = [%[1]s];
|
||||||
GO
|
GO
|
||||||
|
|
||||||
USE [%[1]s];
|
USE [%[1]s];
|
||||||
@@ -69,7 +75,7 @@ GO
|
|||||||
|
|
||||||
IF IS_ROLEMEMBER(N'db_datareader', N'%[2]s') = 0 ALTER ROLE db_datareader ADD MEMBER [%[2]s];
|
IF IS_ROLEMEMBER(N'db_datareader', N'%[2]s') = 0 ALTER ROLE db_datareader ADD MEMBER [%[2]s];
|
||||||
GO
|
GO
|
||||||
`, db, login)
|
`, db, login, pw)
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +83,8 @@ GO
|
|||||||
// Без параметров — по сохранённым настройкам; download=0 — показать текстом (предпросмотр в мастере).
|
// Без параметров — по сохранённым настройкам; download=0 — показать текстом (предпросмотр в мастере).
|
||||||
func (h *setupHandlers) fancyDepoLoginScript(w http.ResponseWriter, r *http.Request) {
|
func (h *setupHandlers) fancyDepoLoginScript(w http.ResponseWriter, r *http.Request) {
|
||||||
s := h.rc.Snapshot().Fancy
|
s := h.rc.Snapshot().Fancy
|
||||||
q := r.URL.Query()
|
_ = r.ParseForm() // пароль приходит только в теле POST, не в адресе
|
||||||
|
q := r.Form
|
||||||
pg := s.IsPostgres()
|
pg := s.IsPostgres()
|
||||||
if t := q.Get("type"); t != "" {
|
if t := q.Get("type"); t != "" {
|
||||||
pg = t == "postgres"
|
pg = t == "postgres"
|
||||||
@@ -90,7 +97,12 @@ func (h *setupHandlers) fancyDepoLoginScript(w http.ResponseWriter, r *http.Requ
|
|||||||
if login == "" {
|
if login == "" {
|
||||||
login = pickStr(strings.TrimSpace(s.User), "bj_user")
|
login = pickStr(strings.TrimSpace(s.User), "bj_user")
|
||||||
}
|
}
|
||||||
body := depoLoginScript(pg, db, login)
|
pass := ""
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
pass = r.PostForm.Get("password")
|
||||||
|
}
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
body := depoLoginScript(pg, db, login, pass)
|
||||||
name := "bj-depo-login-mssql.sql"
|
name := "bj-depo-login-mssql.sql"
|
||||||
if pg {
|
if pg {
|
||||||
name = "bj-depo-login-postgres.sql"
|
name = "bj-depo-login-postgres.sql"
|
||||||
|
|||||||
@@ -6,19 +6,31 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestDepoLoginScript(t *testing.T) {
|
func TestDepoLoginScript(t *testing.T) {
|
||||||
ms := depoLoginScript(false, "CLIENT_DEPO", "bj_svc")
|
ms := depoLoginScript(false, "CLIENT_DEPO", "bj_svc", "")
|
||||||
for _, w := range []string{"CREATE LOGIN [bj_svc]", "USE [CLIENT_DEPO]", "CREATE USER [bj_svc] FOR LOGIN [bj_svc]", "db_datareader", "ALTER USER [bj_svc] WITH LOGIN = [bj_svc]"} {
|
for _, w := range []string{"CREATE LOGIN [bj_svc]", "USE [CLIENT_DEPO]", "CREATE USER [bj_svc] FOR LOGIN [bj_svc]", "db_datareader", "ALTER USER [bj_svc] WITH LOGIN = [bj_svc]"} {
|
||||||
if !strings.Contains(ms, w) {
|
if !strings.Contains(ms, w) {
|
||||||
t.Fatalf("MS SQL: нет %q", w)
|
t.Fatalf("MS SQL: нет %q", w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pg := depoLoginScript(true, "depo", "bj_svc")
|
pg := depoLoginScript(true, "depo", "bj_svc", "")
|
||||||
for _, w := range []string{`CREATE ROLE "bj_svc" LOGIN`, `GRANT CONNECT ON DATABASE "depo"`, `GRANT SELECT ON ALL TABLES`} {
|
for _, w := range []string{`CREATE ROLE "bj_svc" LOGIN`, `GRANT CONNECT ON DATABASE "depo"`, `GRANT SELECT ON ALL TABLES`} {
|
||||||
if !strings.Contains(pg, w) {
|
if !strings.Contains(pg, w) {
|
||||||
t.Fatalf("PostgreSQL: нет %q", w)
|
t.Fatalf("PostgreSQL: нет %q", w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s := depoLoginScript(false, "bad;db", "x'; DROP"); !strings.Contains(s, "[BJ_DEPO_DATA]") || !strings.Contains(s, "[bj_user]") {
|
if s := depoLoginScript(false, "bad;db", "x'; DROP", ""); !strings.Contains(s, "[BJ_DEPO_DATA]") || !strings.Contains(s, "[bj_user]") {
|
||||||
t.Fatal("недопустимые имена — подставляются значения по умолчанию")
|
t.Fatal("недопустимые имена — подставляются значения по умолчанию")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDepoLoginScriptPassword(t *testing.T) {
|
||||||
|
if s := depoLoginScript(false, "D", "u", "Pa'ss"); !strings.Contains(s, "PASSWORD = N'Pa''ss'") || strings.Contains(s, "N'ЗАМЕНИТЕ_НА_ПАРОЛЬ'") {
|
||||||
|
t.Fatal("MS SQL: пароль из поля с экранированием кавычки")
|
||||||
|
}
|
||||||
|
if s := depoLoginScript(true, "d", "u", "p'w%x"); !strings.Contains(s, "PASSWORD 'p''w%x'") {
|
||||||
|
t.Fatal("PostgreSQL: пароль из поля")
|
||||||
|
}
|
||||||
|
if s := depoLoginScript(false, "D", "u", ""); !strings.Contains(s, "N'ЗАМЕНИТЕ_НА_ПАРОЛЬ'") {
|
||||||
|
t.Fatal("пустой пароль — заглушка")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -119,26 +119,40 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
|
|||||||
<li><b>чтение</b> учётных таблиц DEPO (лица, счета, остатки, бумаги) — для сверки переводов;</li>
|
<li><b>чтение</b> учётных таблиц DEPO (лица, счета, остатки, бумаги) — для сверки переводов;</li>
|
||||||
<li><b>запись только в таблицы обмена</b> <code>FTV_M2M*</code> — их права выдаются на следующем экране, вместе с созданием таблиц.</li>
|
<li><b>запись только в таблицы обмена</b> <code>FTV_M2M*</code> — их права выдаются на следующем экране, вместе с созданием таблиц.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p style="margin:0 0 6px">Вариант 1 — администратор создаёт логин сам с этими правами. Вариант 2 — передайте ему скрипт: в нём уже подставлены тип СУБД, база и имя логина из полей выше, нужно только заменить пароль.</p>
|
<p style="margin:0 0 6px">Вариант 1 — администратор создаёт логин сам с этими правами. Вариант 2 — передайте ему скрипт: в нём подставлены тип СУБД, база, имя логина и пароль из полей выше (если пароль не введён — впишите его в скрипт сами). Скрипт с паролем не храните.</p>
|
||||||
<a id="wiz-login-dl" href="/admin/setup/fancy/depo-login/script.sql">Скачать скрипт создания логина</a>
|
<a id="wiz-login-dl" href="#">Скачать скрипт создания логина</a> <span class="muted" id="wiz-login-pwnote" style="font-size:12px"></span>
|
||||||
<pre class="copyable" id="wiz-login-pre" style="max-height:260px;margin-top:6px;font-size:12px">…</pre>
|
<pre class="copyable" id="wiz-login-pre" style="max-height:260px;margin-top:6px;font-size:12px">…</pre>
|
||||||
<p class="muted" style="margin:4px 0 0">После выполнения скрипта впишите логин и пароль в поля выше и нажмите «Проверить и далее».</p>
|
<p class="muted" style="margin:4px 0 0">После выполнения скрипта впишите логин и пароль в поля выше и нажмите «Проверить и далее».</p>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<script>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
var f=document.currentScript.closest('form'), d=document.getElementById('wiz-login-help'), pre=document.getElementById('wiz-login-pre'), a=document.getElementById('wiz-login-dl');
|
var f=document.currentScript.closest('form'), d=document.getElementById('wiz-login-help'), pre=document.getElementById('wiz-login-pre'), a=document.getElementById('wiz-login-dl'), note=document.getElementById('wiz-login-pwnote');
|
||||||
function q(){
|
function val(n){ var e=f.querySelector('[name='+n+']'); return e?e.value:''; }
|
||||||
var t=f.querySelector('[name=db_type]'), db=f.querySelector('[name=depo_database]'), u=f.querySelector('[name=user]');
|
function params(){
|
||||||
return 'type='+encodeURIComponent(t?t.value:'mssql')+'&db='+encodeURIComponent(db&&db.value.trim()?db.value.trim():(db?db.placeholder:''))+'&login='+encodeURIComponent(u&&u.value.trim()?u.value.trim():'bj_user');
|
var db=f.querySelector('[name=depo_database]');
|
||||||
|
var p=new URLSearchParams();
|
||||||
|
p.set('type', val('db_type')||'mssql');
|
||||||
|
p.set('db', db&&db.value.trim()?db.value.trim():(db?db.placeholder:''));
|
||||||
|
p.set('login', val('user').trim()||'bj_user');
|
||||||
|
p.set('password', val('password')); // только в теле POST — не в адресе и не в журналах
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
function upd(){
|
function upd(){
|
||||||
a.href='/admin/setup/fancy/depo-login/script.sql?'+q();
|
note.textContent = val('password') ? '— пароль из поля подставлен в скрипт' : '— пароль не введён: в скрипте заглушка, замените её';
|
||||||
if(!d.open) return;
|
if(!d.open) return;
|
||||||
fetch('/admin/setup/fancy/depo-login/script.sql?download=0&'+q(),{cache:'no-store'}).then(function(r){return r.text()}).then(function(t){pre.textContent=t}).catch(function(){});
|
var p=params(); p.set('download','0');
|
||||||
|
fetch('/admin/setup/fancy/depo-login/script.sql',{method:'POST',body:p,cache:'no-store'}).then(function(r){return r.text()}).then(function(t){pre.textContent=t}).catch(function(){});
|
||||||
}
|
}
|
||||||
|
a.addEventListener('click', function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
var p=params(), fm=document.createElement('form');
|
||||||
|
fm.method='POST'; fm.action='/admin/setup/fancy/depo-login/script.sql'; fm.style.display='none';
|
||||||
|
p.forEach(function(v,k){ var i=document.createElement('input'); i.type='hidden'; i.name=k; i.value=v; fm.appendChild(i); });
|
||||||
|
document.body.appendChild(fm); fm.submit(); fm.remove();
|
||||||
|
});
|
||||||
d.addEventListener('toggle',upd);
|
d.addEventListener('toggle',upd);
|
||||||
['db_type','depo_database','user'].forEach(function(n){var e=f.querySelector('[name='+n+']'); if(e) e.addEventListener('change',upd);});
|
['db_type','depo_database','user','password'].forEach(function(n){var e=f.querySelector('[name='+n+']'); if(e){ e.addEventListener('change',upd); e.addEventListener('input',function(){ clearTimeout(e._t); e._t=setTimeout(upd,400); }); }});
|
||||||
upd();
|
upd();
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user