[FIX] Vocea scaffold: rotta tenantInfo non versionata + portalUrl reale + namespace vault
Correzioni dallo smoke live su api.vocea.cloud (23/6, VIGILE/Vocea):
- tenantInfo(): /tenants/{slug}/info è sotto /api/wb (NON /api/wb/v1) → request() ora
accetta baseOverride; aggiunto $apiBase (base senza /vN). I 3 /integrations/* restano /v1.
- portalUrl(): path reale /{locale}/segnala/{slug} (era /wb/{slug}); aggiunto
portalStatusUrl() /{locale}/stato/{slug}; channelStatus espone portal_status_url.
- Vault namespace corretto: tier1__nis2-app__vocea (l'app reale è nis2-app, non nis2-agile).
Scaffold resta DORMIENTE (VOCEA_ENABLED=false). NB: VIGILE segnala che le route
/api/wb/v1/integrations sono ancora 404 (deploy MS incompleto) → non attivare finché
Vocea non completa il deploy e l'API key non è nel vault.
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
637e1ec510
commit
d45bd472cd
@@ -108,7 +108,7 @@ define('ANTHROPIC_MAX_TOKENS', Env::int('ANTHROPIC_MAX_TOKENS', 4096));
|
|||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// SCAFFOLD DORMIENTE: con VOCEA_ENABLED=false (default) VoceaService non effettua
|
// SCAFFOLD DORMIENTE: con VOCEA_ENABLED=false (default) VoceaService non effettua
|
||||||
// nessuna chiamata di rete. Attivazione a MS live: applicare migrate_063 +
|
// nessuna chiamata di rete. Attivazione a MS live: applicare migrate_063 +
|
||||||
// VOCEA_ENABLED=true + VOCEA_API_KEY (vault tier1__nis2-agile__vocea__*) +
|
// VOCEA_ENABLED=true + VOCEA_API_KEY (vault tier1__nis2-app__vocea__*) +
|
||||||
// slug del canale su organizations.vocea_tenant_slug (status='active').
|
// slug del canale su organizations.vocea_tenant_slug (status='active').
|
||||||
define('VOCEA_ENABLED', filter_var(Env::get('VOCEA_ENABLED', 'false'), FILTER_VALIDATE_BOOLEAN));
|
define('VOCEA_ENABLED', filter_var(Env::get('VOCEA_ENABLED', 'false'), FILTER_VALIDATE_BOOLEAN));
|
||||||
define('VOCEA_BASE_URL', Env::get('VOCEA_BASE_URL', 'https://api.vocea.cloud/api/wb/v1')); // API integratori (X-API-Key)
|
define('VOCEA_BASE_URL', Env::get('VOCEA_BASE_URL', 'https://api.vocea.cloud/api/wb/v1')); // API integratori (X-API-Key)
|
||||||
|
|||||||
@@ -419,6 +419,7 @@ class WhistleblowingController extends BaseController
|
|||||||
'channel_status' => $status,
|
'channel_status' => $status,
|
||||||
'provisioned' => $enabled && !empty($slug) && $status === 'active',
|
'provisioned' => $enabled && !empty($slug) && $status === 'active',
|
||||||
'portal_url' => !empty($slug) ? VoceaService::portalUrl($slug) : null,
|
'portal_url' => !empty($slug) ? VoceaService::portalUrl($slug) : null,
|
||||||
|
'portal_status_url' => !empty($slug) ? VoceaService::portalStatusUrl($slug) : null,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
* SCAFFOLD DORMIENTE: finché VOCEA_ENABLED=false (default) ogni metodo che richiede
|
* SCAFFOLD DORMIENTE: finché VOCEA_ENABLED=false (default) ogni metodo che richiede
|
||||||
* rete ritorna ['ok'=>false,'error'=>'DISABLED']. Si "accende" così:
|
* rete ritorna ['ok'=>false,'error'=>'DISABLED']. Si "accende" così:
|
||||||
* 1. applicare migrate_063_vocea_channel.php (colonne organizations.vocea_*)
|
* 1. applicare migrate_063_vocea_channel.php (colonne organizations.vocea_*)
|
||||||
* 2. VOCEA_ENABLED=true + VOCEA_API_KEY (vault tier1__nis2-agile__vocea__*)
|
* 2. VOCEA_ENABLED=true + VOCEA_API_KEY (vault tier1__nis2-app__vocea__*)
|
||||||
* 3. impostare organizations.vocea_tenant_slug + vocea_channel_status='active' sul canale
|
* 3. impostare organizations.vocea_tenant_slug + vocea_channel_status='active' sul canale
|
||||||
*
|
*
|
||||||
* CONFINE ZERO-KNOWLEDGE: l'API NON ritorna MAI contenuti in chiaro. listReports
|
* CONFINE ZERO-KNOWLEDGE: l'API NON ritorna MAI contenuti in chiaro. listReports
|
||||||
@@ -32,11 +32,14 @@ class VoceaService
|
|||||||
private const CONNECT_TIMEOUT_SEC = 6;
|
private const CONNECT_TIMEOUT_SEC = 6;
|
||||||
|
|
||||||
private string $baseUrl;
|
private string $baseUrl;
|
||||||
|
private string $apiBase; // base NON versionata (endpoint pubblici tipo /tenants/{slug}/info)
|
||||||
private string $apiKey;
|
private string $apiKey;
|
||||||
|
|
||||||
public function __construct(?string $apiKey = null)
|
public function __construct(?string $apiKey = null)
|
||||||
{
|
{
|
||||||
$this->baseUrl = rtrim(defined('VOCEA_BASE_URL') ? VOCEA_BASE_URL : 'https://api.vocea.cloud/api/wb/v1', '/');
|
$this->baseUrl = rtrim(defined('VOCEA_BASE_URL') ? VOCEA_BASE_URL : 'https://api.vocea.cloud/api/wb/v1', '/');
|
||||||
|
// gli endpoint /integrations/* sono versionati (/v1); gli endpoint pubblici (/tenants/{slug}/info) NO.
|
||||||
|
$this->apiBase = preg_replace('#/v\d+$#', '', $this->baseUrl);
|
||||||
$this->apiKey = $apiKey ?? (defined('VOCEA_API_KEY') ? VOCEA_API_KEY : '');
|
$this->apiKey = $apiKey ?? (defined('VOCEA_API_KEY') ? VOCEA_API_KEY : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,22 +59,31 @@ class VoceaService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL del portale segnalante da embeddare via wb-link.js.
|
* URL del portale segnalante (per embed via wb-link.js).
|
||||||
* NB: iframe diretto è bloccato dalla CSP `frame-ancestors 'self'` di Vocea →
|
* NB: iframe diretto è bloccato dalla CSP `frame-ancestors 'self'` di Vocea →
|
||||||
* usare il widget wb-link.js oppure far allargare la CSP al dominio NIS2.
|
* usare il widget wb-link.js oppure far allargare la CSP al dominio NIS2.
|
||||||
|
* Path reale (confermato smoke 23/6): /{locale}/segnala/{slug}.
|
||||||
*/
|
*/
|
||||||
public static function portalUrl(string $slug): string
|
public static function portalUrl(string $slug, string $locale = 'it'): string
|
||||||
{
|
{
|
||||||
$base = rtrim(defined('VOCEA_PORTAL_URL') ? VOCEA_PORTAL_URL : 'https://app.vocea.cloud', '/');
|
$base = rtrim(defined('VOCEA_PORTAL_URL') ? VOCEA_PORTAL_URL : 'https://app.vocea.cloud', '/');
|
||||||
return "{$base}/wb/" . rawurlencode($slug);
|
return "{$base}/" . rawurlencode($locale) . '/segnala/' . rawurlencode($slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** URL portale per il tracciamento stato di una segnalazione (/{locale}/stato/{slug}). */
|
||||||
|
public static function portalStatusUrl(string $slug, string $locale = 'it'): string
|
||||||
|
{
|
||||||
|
$base = rtrim(defined('VOCEA_PORTAL_URL') ? VOCEA_PORTAL_URL : 'https://app.vocea.cloud', '/');
|
||||||
|
return "{$base}/" . rawurlencode($locale) . '/stato/' . rawurlencode($slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Read / management (server-to-server, API key) ────────────────────────
|
// ── Read / management (server-to-server, API key) ────────────────────────
|
||||||
|
|
||||||
/** Public key del canale (endpoint pubblico, no API key). */
|
/** Public key del canale (endpoint pubblico NON versionato, no API key). */
|
||||||
public function tenantInfo(string $slug): array
|
public function tenantInfo(string $slug): array
|
||||||
{
|
{
|
||||||
return $this->request('GET', '/tenants/' . rawurlencode($slug) . '/info', null, false);
|
// /tenants/{slug}/info vive sotto /api/wb (NON /api/wb/v1) — confermato smoke 23/6.
|
||||||
|
return $this->request('GET', '/tenants/' . rawurlencode($slug) . '/info', null, false, $this->apiBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Lista metadati segnalazioni del canale (paginata, SENZA plaintext). */
|
/** Lista metadati segnalazioni del canale (paginata, SENZA plaintext). */
|
||||||
@@ -107,21 +119,22 @@ class VoceaService
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bool $auth Se true invia X-API-Key (default). Endpoint pubblici (tenantInfo) → false.
|
* @param bool $auth Se true invia X-API-Key (default). Endpoint pubblici (tenantInfo) → false.
|
||||||
|
* @param ?string $baseOverride Base URL alternativa (es. NON versionata per /tenants/{slug}/info).
|
||||||
* @return array ['ok'=>bool,'status'=>int,'data'=>mixed,'error'=>?string]
|
* @return array ['ok'=>bool,'status'=>int,'data'=>mixed,'error'=>?string]
|
||||||
*/
|
*/
|
||||||
private function request(string $method, string $path, ?array $body = null, bool $auth = true): array
|
private function request(string $method, string $path, ?array $body = null, bool $auth = true, ?string $baseOverride = null): array
|
||||||
{
|
{
|
||||||
if (!self::enabled()) {
|
if (!self::enabled()) {
|
||||||
return ['ok' => false, 'error' => 'DISABLED', 'message' => 'Integrazione Vocea non attiva (VOCEA_ENABLED=false).'];
|
return ['ok' => false, 'error' => 'DISABLED', 'message' => 'Integrazione Vocea non attiva (VOCEA_ENABLED=false).'];
|
||||||
}
|
}
|
||||||
if ($auth && $this->apiKey === '') {
|
if ($auth && $this->apiKey === '') {
|
||||||
return ['ok' => false, 'error' => 'NOT_PROVISIONED', 'message' => 'VOCEA_API_KEY non configurata (vault tier1__nis2-agile__vocea__*).'];
|
return ['ok' => false, 'error' => 'NOT_PROVISIONED', 'message' => 'VOCEA_API_KEY non configurata (vault tier1__nis2-app__vocea__*).'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$headers = ['Accept: application/json'];
|
$headers = ['Accept: application/json'];
|
||||||
if ($auth) { $headers[] = 'X-API-Key: ' . $this->apiKey; }
|
if ($auth) { $headers[] = 'X-API-Key: ' . $this->apiKey; }
|
||||||
|
|
||||||
$ch = curl_init($this->baseUrl . $path);
|
$ch = curl_init(($baseOverride ?? $this->baseUrl) . $path);
|
||||||
$opts = [
|
$opts = [
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
CURLOPT_CUSTOMREQUEST => $method,
|
CURLOPT_CUSTOMREQUEST => $method,
|
||||||
|
|||||||
Reference in New Issue
Block a user