fix(реклама): GRANT crm_supplier_worker на ad_campaigns/ad_campaign_ads + ad_settings по дефолту — иначе джобы Директа падают permission denied на проде

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-07-25 00:25:01 +03:00
parent 60128d48d8
commit 5010087b4f
3 changed files with 73 additions and 1 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ class ChargeCampaignSpendJob implements ShouldQueue
$stopAll = app(AdStopAllService::class);
$date = Carbon::now()->subDay()->toDateString();
$markup = new AdMarkup((string) (DB::connection('pgsql_supplier')->table('ad_settings')->value('markup_percent') ?? '30.00'));
$markup = new AdMarkup((string) (DB::table('ad_settings')->value('markup_percent') ?? '30.00'));
$direct = new YandexDirectClient(
$this->configString('services.yandex_direct.base_url'),
$this->configString('services.yandex_direct.token'),
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
// Джобы Директа (Charge/SyncAudience/SyncModeration) перечисляют и обновляют
// ad_campaigns / ad_campaign_ads через соединение pgsql_supplier (роль
// crm_supplier_worker, BYPASSRLS). BYPASSRLS обходит RLS-политику, но НЕ
// заменяет табличную привилегию — без GRANT будет permission denied на проде
// (как sales_prospects 19.07). SELECT — чтение всеми тремя джобами; UPDATE —
// запись статусов модерации SyncCampaignModerationJob. Гард на существование
// роли: на dev/test роли нет (DB_USERNAME=postgres superuser).
DB::statement(<<<'SQL'
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_supplier_worker') THEN
GRANT SELECT, UPDATE ON ad_campaigns TO crm_supplier_worker;
GRANT SELECT, UPDATE ON ad_campaign_ads TO crm_supplier_worker;
END IF;
END
$$;
SQL);
}
public function down(): void
{
DB::statement(<<<'SQL'
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_supplier_worker') THEN
REVOKE SELECT, UPDATE ON ad_campaigns FROM crm_supplier_worker;
REVOKE SELECT, UPDATE ON ad_campaign_ads FROM crm_supplier_worker;
END IF;
END
$$;
SQL);
}
};
+28
View File
@@ -8,6 +8,34 @@
> параллельно с боевым main. Их прежние номера (v8.59–v8.62) **столкнулись** с боевыми (автоподбор),
> поэтому при сведении они перенумерованы. Содержание не менялось.
## v8.94 (2026-07-25) — GRANT crm_supplier_worker на ad_campaigns/ad_campaign_ads — доступ ночных джобов Директа
rls-reviewer подтвердил дыру: три ночных джоба Директа (Charge/SyncAudience/
SyncModeration) перечисляют и обновляют `ad_campaigns`/`ad_campaign_ads`
через соединение `pgsql_supplier` (роль `crm_supplier_worker`, BYPASSRLS).
BYPASSRLS снимает RLS-политику, но НЕ заменяет табличную привилегию — без
GRANT на проде было бы `permission denied for table ad_campaigns/
ad_campaign_ads` (тот же класс сбоя, что sales_prospects 19.07):
```sql
GRANT SELECT, UPDATE ON ad_campaigns TO crm_supplier_worker;
GRANT SELECT, UPDATE ON ad_campaign_ads TO crm_supplier_worker;
```
SELECT — чтение всеми тремя джобами; UPDATE — запись `moderation_status`
через SyncCampaignModerationJob. Миграция
`app/database/migrations/2026_07_25_100400_grant_supplier_worker_advertising.php`
(`down()` — симметричный REVOKE), гард на существование роли (на dev/тест
роли нет), прогнана на `liderra_testing` — DONE.
Той же задачей `ChargeCampaignSpendJob` переведён на чтение `ad_settings`
(наценка) по ДЕФОЛТНОМУ соединению вместо `pgsql_supplier` — таблица
глобальная, без RLS, `crm_app_user` уже имеет `SELECT` из миграции
2026_07_24_100300; отдельный supplier-грант не нужен.
Структурно: 0 новых таблиц/колонок/индексов, +2 GRANT-выражения.
Функций/триггеров без изменений.
## v8.93 (2026-07-25) — Яндекс-канал, Часть B1, Task 5 — GRANT crm_admin_user на ad_* + модели Eloquent
Закрывает совет B-adv-1 из RLS-ревью Части A: будущему админ-экрану