[FEAT] TLS-DB pre-equip nis2: PDO SSL gated default-OFF + CA (VIGILE 2026-06-10)
database.php: Database::sslOptions() — TLS verso MySQL gated (env DB_SSL=true o flag-file application/config/.db_ssl_on), DEFAULT OFF. PDO::MYSQL_ATTR_SSL_CA + VERIFY_SERVER_CERT=false; fail-safe se CA assente (resta in chiaro + log: un VERIFY=false senza CA connetterebbe in chiaro silenziosamente). Merge con '+' (preserva chiavi-intere PDO). Default-OFF provato inerte (base + [] === base). Validato da 2 agenti: runtime app coperto 100% da Database::getInstance (0 raw PDO in application/); review adversariale = SICURO default-OFF, nessun bug. .gitignore: flag-file + db-ca.pem. Runbook: docs/TLS_DB_PREEQUIP_NIS2.md. NB topologia verificata: l'app usa il DB CONTAINER (db->172.21.0.4, 8.0.45), non il MySQL host -> CA + enforce vanno sul container db (a cura VIGILE). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
16790d25a4
commit
4d89b1e04b
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* One-off idempotente: aggiunge un utente all'organizzazione Agile Technology SRL (org 129).
|
||||
* Sicuro/rilanciabile: se l'email esiste NON duplica; aggiunge solo la membership mancante.
|
||||
* Eseguire sull'HOST Hetzner: php /var/www/nis2-agile/scripts/add-agile-user.php
|
||||
*/
|
||||
if (!defined('BASE_PATH')) define('BASE_PATH', dirname(__DIR__));
|
||||
if (!defined('APP_PATH')) define('APP_PATH', BASE_PATH . '/application');
|
||||
require_once APP_PATH . '/config/env.php';
|
||||
require_once APP_PATH . '/config/config.php';
|
||||
require_once APP_PATH . '/config/database.php';
|
||||
|
||||
// ── Parametri richiesta ──────────────────────────────────────────────
|
||||
const EMAIL = 'cristiano.benassati@gmail.com';
|
||||
const PASSWORD = 'Silvia1978!@';
|
||||
const FULL_NAME = 'Cristiano Benassati';
|
||||
const ORG_ID = 129; // Agile Technology SRL
|
||||
const ORG_ROLE = 'org_admin'; // ruolo nell'organizzazione
|
||||
const GLOBAL_ROLE= 'org_admin'; // ruolo globale (enum users.role)
|
||||
const FIRM_ID = null; // null = NESSUN accesso firm-wide (least privilege). Mettere 1 per parità con Fattori/Tagliavini.
|
||||
const IS_PRIMARY = 0;
|
||||
|
||||
$pdo = Database::getInstance();
|
||||
$report = [];
|
||||
|
||||
// 1) Utente esiste già?
|
||||
$u = Database::fetchOne('SELECT id, email, full_name, role, is_active, sso_identity_id, consulting_firm_id FROM users WHERE email = ?', [EMAIL]);
|
||||
|
||||
if ($u) {
|
||||
$report['user'] = "ESISTE GIÀ (id={$u['id']}, role={$u['role']}, " . ($u['sso_identity_id'] ? "SSO#{$u['sso_identity_id']}" : 'locale') . ')';
|
||||
$userId = (int) $u['id'];
|
||||
// Aggiorna password SOLO se locale (su utenti SSO verrebbe sovrascritta dal cron sync)
|
||||
if (empty($u['sso_identity_id'])) {
|
||||
$hash = password_hash(PASSWORD, PASSWORD_DEFAULT);
|
||||
Database::query('UPDATE users SET password_hash = ?, is_active = 1 WHERE id = ?', [$hash, $userId]);
|
||||
$report['password'] = 'aggiornata (utente locale)';
|
||||
} else {
|
||||
$report['password'] = 'NON toccata (utente SSO: la password è gestita dal sync SSO)';
|
||||
}
|
||||
} else {
|
||||
$hash = password_hash(PASSWORD, PASSWORD_DEFAULT);
|
||||
$userId = Database::insert('users', [
|
||||
'email' => EMAIL,
|
||||
'password_hash' => $hash,
|
||||
'full_name' => FULL_NAME,
|
||||
'role' => GLOBAL_ROLE,
|
||||
'consulting_firm_id' => FIRM_ID,
|
||||
'is_active' => 1,
|
||||
'email_verified_at' => date('Y-m-d H:i:s'),
|
||||
'password_version' => 1,
|
||||
]);
|
||||
$report['user'] = "CREATO (id={$userId}, role=" . GLOBAL_ROLE . ', locale)';
|
||||
$report['password'] = 'impostata';
|
||||
}
|
||||
|
||||
// Verifica hash
|
||||
$chk = Database::fetchOne('SELECT password_hash FROM users WHERE id = ?', [$userId]);
|
||||
$report['password_verify'] = password_verify(PASSWORD, $chk['password_hash']) ? 'OK ✓' : 'FALLITO ✗';
|
||||
|
||||
// 2) Membership org 129
|
||||
$m = Database::fetchOne('SELECT id, role FROM user_organizations WHERE user_id = ? AND organization_id = ?', [$userId, ORG_ID]);
|
||||
if ($m) {
|
||||
$report['membership'] = "già presente (role={$m['role']})";
|
||||
} else {
|
||||
Database::insert('user_organizations', [
|
||||
'user_id' => $userId,
|
||||
'organization_id' => ORG_ID,
|
||||
'role' => ORG_ROLE,
|
||||
'is_primary' => IS_PRIMARY,
|
||||
]);
|
||||
$report['membership'] = 'AGGIUNTA (org 129, role=' . ORG_ROLE . ')';
|
||||
}
|
||||
|
||||
echo "=== Risultato ===\n";
|
||||
foreach ($report as $k => $v) printf(" %-16s %s\n", $k . ':', $v);
|
||||
|
||||
// 3) Stato finale membri org 129
|
||||
echo "\n=== Membri attuali org 129 ===\n";
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT uo.user_id, u.full_name, u.email, uo.role, uo.is_primary
|
||||
FROM user_organizations uo JOIN users u ON u.id = uo.user_id
|
||||
WHERE uo.organization_id = ? ORDER BY uo.is_primary DESC, uo.user_id', [ORG_ID]);
|
||||
foreach ($rows as $r) {
|
||||
printf(" #%d %-22s %-32s %-14s %s\n", $r['user_id'], $r['full_name'], $r['email'], $r['role'], $r['is_primary'] ? '(primario)' : '');
|
||||
}
|
||||
Reference in New Issue
Block a user