__cronOverrideRole) ? DelegheRoles::RP : $this->__cronOverrideRole;
$userCodiceFiscale = empty($this->__cronOverrideCF) ? $this->getUserFromRole($userRole, RoleSyncBehaviour::NEVER_SYNC) : $this->__cronOverrideCF;
} elseif (!empty($_SESSION["utente"]) && method_exists($_SESSION["utente"], "getInfo")) {
$userCodiceFiscale = $_SESSION["utente"]->getInfo()["cf"];
}
}
if(empty($userRole)) {
$userRole = $this->getUserRole($userCodiceFiscale, null, RoleSyncBehaviour::NEVER_SYNC);
}
// Gather delle info sulla stazione appaltante
if(empty($saCodiceFiscale) && empty($saCodiceAusa)) {
if(isset($this->overrideStazioneAppaltante)) {
$saCodiceFiscale = $this->overrideStazioneAppaltante["codiceFiscale"];
$saCodiceAusa = $this->overrideStazioneAppaltante["codiceAusa"];
} else {
// Proviamo se la delega corrente prevede info della SA
$infoSA = $this->getFullDelegaInfo($userCodiceFiscale);
if(!empty($infoSA) && !empty($infoSA["sa_cf"]) && !empty($infoSA["sa_ausa"])) {
$saCodiceFiscale = $infoSA["sa_cf"];
$saCodiceAusa = $infoSA["sa_ausa"];
} else {
// Proviamo poi dal fascicolo
if (!empty($this->fascicolo["data"])) {
$data = unserialize($this->fascicolo["data"]);
}
$saCodiceFiscale = $data["benficiario"]["codice_fiscale"] ?? null;
$saCodiceAusa = $data["benficiario"]["codice_ausa"] ?? null;
// Poi dall'ente
if (isset($_SESSION["utente"]) && isset($_SESSION["ente"]) && (!defined("__CMDS_JOB_EXEC") || empty(__CMDS_JOB_EXEC))) {
$ente = (!empty($_SESSION["utente"]->codice_ente)) ? $_SESSION["utente"]->codice_ente : $_SESSION["ente"]->codice;
if (!empty($ente)) {
$ente = Ente::getInfoFromID($ente);
if (!empty($ente)) {
$ente = reset($ente);
if (!empty($ente["cf"]) && !empty($ente["codice_ausa"]) && $ente["cf"] != $saCodiceFiscale) {
$saCodiceFiscale = $ente["cf"];
$saCodiceAusa = $ente["codice_ausa"];
}
}
}
}
}
}
} else {
if(empty($saCodiceFiscale) || empty($saCodiceAusa)) {
throw new Exception("Se viene specificato il CF della Stazione Appaltante è necessario specificare anche il codice AUSA e viceversa");
}
}
// Gathering del codice fiscale e ruolo
$userCodiceFiscale = trim($userCodiceFiscale);
if (!in_array($userRole, DelegheRoles::VALID_ROLES)) {
return [];
}
$userLoa = $_SESSION["utente"]->authenticationLevel + 1;
return [
"userCodiceFiscale" => $userCodiceFiscale,
// codice fiscale dell'utente connesso alla piattaforma e operante per conto della Stazione appaltante
"userRole" => $userRole,
// ruolo dell'utente. RP = Responsabile del Porgetto; DRP = Delegato del Responsabile del Porgetto [RP, DRP]
"userLoa" => "{$userLoa}",
// level of assurance adottato dalla piattaforma per l'identificazione dell'utente [3, 4]
"userIdpType" => (!empty($_SESSION["utente"]->publicSSO)) ? $_SESSION["utente"]->publicSSO : "CUSTOM",
// tipo di identity provider utilizzato per stabilire l'identità dell'utente.
"SACodiceFiscale" => DEVELOP_ENV && isset($config["DEV"]["SACodiceFiscale"]) ? $config["DEV"]["SACodiceFiscale"] : ($saCodiceFiscale),
// codice Fiscale della stazione appaltante. Può essere nullo in caso di soggetti non dotati di personalità giuridica
"SAcodiceAUSA" => DEVELOP_ENV && isset($config["DEV"]["SACodiceAusa"]) ? $config["DEV"]["SACodiceAusa"] : ($saCodiceAusa),
// codice ausa della stazione appaltante alla quale appartiene l'utente
"regCodicePiattaforma" => $config["pdnd"]["platform"]["regCodicePiattaforma"],
// codice identificativo della piattaforma
"regCodiceComponente" => $config["pdnd"]["platform"]["regCodiceComponente"],
// codice identificativo del componente client
"businessFlowID" => $businessFlowID,
// coincide con idAppalto. Assume valore "00000000-0000-0000-0000-000000000000" nella prima transazione (che è necessariamente comunicaAppalto.crea-appalto) In tutte le operazioni successive riconduce la transazione all’appalto
"traceID" => \Ramsey\Uuid\Uuid::uuid6()->toString(),
// identificativo univoco assegnato dalla piattaforma alla richiesta
"spanID" => \Ramsey\Uuid\Uuid::uuid6()->toString(),
// identificativo univoco assegnato dalla piattaforma (?) all'operazione iniziale richiesta dall'utente
"aud" => $aud,
// Audience
];
}
/**
* Effettua una chiamata API verso NPA
*
* @param String $apiEndpoint Endpoint da contattare
* @param String $method Metodo da contattare
* @param array $body Corpo della richiesta
* @param array|null $claims Claim per la generazione dei voucher
* @param array|null $header
* @param string|null $message Viene popolato con u neventuale messaggio d'errore
* @param int $retry401 Viene usato per tenere traccia se la richiesta corrente è un tentativo per autenticazione fallita
* @return String
*/
public function apiCall(String $apiEndpoint, String $method, array $body, ?array $claims = null, ?array $header = [], ?string &$message = null, int $retry401 = Retries401::NORMAL): String
{
// Cerchiamo le config
$endpointConfig = $this->api_config[$apiEndpoint] ?? null;
$methodConfig = $endpointConfig["methods"][$method] ?? null;
if ($endpointConfig == null || $methodConfig == null) {
throw new Exception("Chiamata API {$apiEndpoint}::{$method} non trovata.");
}
// Verifichiamo che l'utente possa fare la chiamata
$canDoRequest = $this->canDoRequest($reason, $apiEndpoint, $method);
if (!$canDoRequest) {
$message = $reason["msg"];
if ($methodConfig["log_auth_failures"] ?? true) {
$this->appendLog(
$apiEndpoint,
$method,
["canDoRequest" => false],
is_array($reason) ? json_encode($reason) : $reason,
);
}
return false;
}
$saveLog = $methodConfig["log"] ?? true;
$url = $endpointConfig["endpoint"] . "/" . $methodConfig["path"];
$request_method = $methodConfig["type"] ?? "GET";
$ch = curl_init();
$url = ltrim($url, "/");
if ($request_method == "POST") {
$send_header = array_merge(["Content-Type: application/json"], $header);
$payload = json_encode($body);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
} else {
$send_header = $header;
$query = "";
if (!empty($body)) {
$query = "?" . http_build_query($body);
}
$url = "{$url}{$query}";
curl_setopt($ch, CURLOPT_URL, $url);
}
// Generazione di un voucher generico
if (empty($claims)) {
$claims = $this->getAnacJwsCustomClaims(
$endpointConfig["endpoint"],
$this->getUUID()
);
}
if (isset($claims["userCodiceFiscale"])) {
// Se è già uppercase skippiamo il secondo tentativo
// Altrimenti il primo tentativo lo manda così com'è
if ($retry401 == Retries401::UPPERCASE ) {
$claims["userCodiceFiscale"] = strtoupper($claims["userCodiceFiscale"]);
// Se è il terzo tentativo proviamo tutto lowercase
} elseif ($retry401 == Retries401::LOWERCASE) {
$claims["userCodiceFiscale"] = strtolower($claims["userCodiceFiscale"]);
}
}
try {
$authorization = PDND::getVoucher(
$endpointConfig["purpose"],
$claims
);
} catch (\Throwable $t) {
$message = DEVELOP_ENV ? $t->getMessage() : "Impossibile contattare i server PDND. Riprova più tardi.";
$this->appendLog(
$apiEndpoint,
$method,
["pdnd" => true],
"PDND error " . $t->getMessage(),
);
return false;
}
// Mancanza di voucher
if ($authorization === null || empty($authorization->access_token)) {
$message = "Impossibile creare Voucher PDND";
$this->appendLog(
$apiEndpoint,
$method,
[
"pdnd" => true,
],
"Can't create voucher."
);
return false;
}
$send_header = array_merge([
"Authorization: Bearer {$authorization->access_token}",
"Agid-JWT-TrackingEvidence: {$authorization->tracking_evidence_jwt}"
], $send_header);
curl_setopt($ch, CURLOPT_HTTPHEADER, $send_header);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $request_method);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
if ($method === "serviceStatus") {
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
}
curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
curl_setopt($ch, CURLOPT_ENCODING, 'UTF-8');
ob_start();
$verbose = fopen('php://temp', 'w+');
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_STDERR, $verbose);
try {
$result = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (isset($claims["userCodiceFiscale"])) {
if($httpCode < 400 && in_array($apiEndpoint, ["pianificazioneAppalto", "comunicaAppalto", "comunicaPostPubblicazione"], true)) {
$delegaAttuale = $this->getFullDelegaInfo($claims["userCodiceFiscale"]);
// Se non c'è delega, possiamo considerarlo il nuovo RP essendo il primo submit
if(empty($delegaAttuale) || empty($delegaAttuale["role"])) {
$role = DelegheRoles::RP;
} else {
$role = $delegaAttuale["role"];
}
// Aggiorniamo la delega
self::setUserRoleToDB($claims["userCodiceFiscale"], $role, $this->fascicolo["codice"], $claims["SACodiceFiscale"], $claims["SAcodiceAUSA"]);
}
}
// Detect 401
$is401 = false;
if ($retry401 < Retries401::MAX) {
$is401 =
empty($result) ||
($httpCode == 401 && is_string($result) && str_contains($result, "SEC05")) ||
($httpCode == 400 && is_string($result) && str_contains($result, "AUT01"));
}
rewind($verbose);
$verbose = stream_get_contents($verbose);
// Workaround per case del codice fiscale
if ($is401) {
// Riproviamo
if ($saveLog) {
$this->appendLog(
$apiEndpoint,
$method,
[
"request" => $verbose,
"payload" => $payload ?? null,
"result" => $result ?? null,
"attempt" => $retry401,
]
);
}
return $this->apiCall($apiEndpoint, $method, $body, $claims, $header, $message, $retry401 + 1);
}
// Estriamo il transaction ID
if (!empty($this->scheda["codice"])) {
$pattern = '/(GovWay-Transaction-ID:)\s(.+\n)/';
preg_match($pattern, $verbose, $matches);
$transactionId = $matches[2] ?? "";
if (!empty($transactionId)) {
$infoScheda = new stdClass();
if (!empty($this->scheda["info"]))
$infoScheda = unserialize($this->scheda["info"]);
$infoScheda->lastTransactionID = $transactionId;
$this->updateScheda(["info" => serialize($infoScheda)]);
}
}
if ($saveLog) {
$this->appendLog(
$apiEndpoint,
$method,
[
"request" => $verbose,
"payload" => $payload ?? null,
"result" => $result ?? null,
]
);
}
curl_close($ch);
return $result;
} catch (\Exception $e) {
$this->appendLog(
$apiEndpoint,
$method,
[
"request" => $verbose,
"payload" => $payload ?? null,
"result" => $result ?? null,
],
$e->getMessage(),
);
return $e->getMessage();
}
}
/**
* Ottiene il permesso per una chiamata API
*
* @param string|null $apiEndpoint
* @param string|null $method
* @param boolean|null $dynamicPermission
* @return array
*/
public function getPermissionFor(?string $apiEndpoint, ?string $method, ?bool &$dynamicPermission = null): array
{
$permission = null;
if ($apiEndpoint !== null && $method !== null) {
// Verifichiamo i permessi se necessario
$endpointConfig = $this->api_config[$apiEndpoint] ?? null;
$methodConfig = $endpointConfig["methods"][$method] ?? null;
// Verifichiamo i permessi, prende priorità quello del metodo
$permission = $methodConfig["permission"] ?? null;
// Il permesso dinamico è in base alla tipologia della scheda
$dynamicPermission = $methodConfig["dynamic_permission"] ?? false;
// Proviamo altrimenti a prendere i permessi generici della categoria
if ($permission === null) {
$permission = $endpointConfig["permission"] ?? null;
$dynamicPermission = $endpointConfig["dynamic_permission"] ?? false;
}
}
// Permessi di default
if ($permission === null) {
$permission = DelegheRoles::ANY;
$dynamicPermission = false;
}
return $permission;
}
/**
* Verifica che l'utente sia abilitato ad effettuare una richiesta
*
* @param array|null $reason Viene popolato con la motivazione per un eventuale diniego
* @param string|null $apiEndpoint API Endpoint da contattare (opzionale)
* @param string|null $method Metodo da contattare (opzionale)
* @return boolean
*/
public function canDoRequest(?array &$reason = null, ?string $apiEndpoint = null, ?string $method = null)
{
if ($apiEndpoint === "ausaController" && $method === "getBy") {
return true;
}
// Siamo un cronjob o simile
$canDoRequest = false;
if (defined("__CMDS_JOB_EXEC") && !empty(__CMDS_JOB_EXEC)) {
return true;
}
// Siamo un utente
$utente = $_SESSION["utente"] ?? null;
// Metodo per evitare nesting eccessivo
do {
// Verifichiamo il livello di autenticazione
if (!$utente || $utente->authenticationLevel < 2) {
$reason = ["code" => "AUTH"];
break;
}
// Sanity check sul RUP
if ($method !== "presa-carico") {
if (isset($this->oggetto) && method_exists($this->oggetto, "getRup")) {
$rp_cf = trim(strtoupper($this->getUserFromRole(DelegheRoles::RP)));
$rup_cf = trim(strtoupper($this->oggetto->getRup()["codice_fiscale"] ?? ""));
if ($rp_cf !== $rup_cf) {
$reason = ["code" => "RUP_RP_MISMATCH"];
break;
}
}
}
// Otteniamo le info utente
$userInfo = method_exists($utente, "getInfo") ? $utente->getInfo() : [];
// Prendiamo il CF dell'utente
$cf = $userInfo["cf"] ?? null;
if (!$cf) {
$reason = ["code" => "USER"];
break;
}
$permission = $this->getPermissionFor($apiEndpoint, $method, $dynamicPermission);
if ($apiEndpoint !== null && $method !== null) {
// Verifichiamo i permessi se necessario
$endpointConfig = $this->api_config[$apiEndpoint] ?? null;
$methodConfig = $endpointConfig["methods"][$method] ?? null;
}
// NO_AUTH non fa verifiche
if ($permission !== null && $permission !== DelegheRoles::NO_AUTH) {
// Otteniamo il permesso dell'utente
$userRole = $this->getUserRole($cf, $permission, RoleSyncBehaviour::NEVER_SYNC);
// Permesso negato
if ($userRole === null || !in_array($userRole, $permission)) {
$permissionText = implode(", ", $permission);
$txtRole = $userRole ?? "Nessuno";
if ($apiEndpoint !== null && $methodConfig !== null) {
$methodFriendlyName = ucfirst(str_replace("-", " ", $methodConfig["path"] ?? ""));
$apiFriendlyName = ucfirst(camelCaseToWords($apiEndpoint));
$msg_pre = "Per effettuare l'operazione {$methodFriendlyName} su {$apiFriendlyName}";
} else {
$msg_pre = "Per usare questo modulo";
}
$reason = [
"code" => "PERMISSION_DENIED",
"msg" => "{$msg_pre} ti serve uno dei seguenti ruoli: {$permissionText}. Ruolo rilevato: {$txtRole}"
];
return false; // Il messaggio viene popolato qui
}
// Se il permesso è dinamico, dobbiamo verificare la tipologia scheda
if ($dynamicPermission && in_array($userRole, DelegheRoles::DRPs)) {
// TODO Implementare verifica tipo scheda
}
}
// Tutti i controlli passati
$canDoRequest = true;
} while (false);
if (!empty($reason)) {
$reason["msg"] = $this->managePermissionMessage($reason["code"]);
}
return $canDoRequest;
}
/**
* managePermissionMessage
* Mostra un messaggio amichevole relativo all'eventuale errore di autorizzazione
*
* @param mixed $code
* @return void
*/
public function managePermissionMessage(String $code)
{
$return = "Errore sconosciuto";
if ($code == "RUP_RP_MISMATCH") {
$rp_cf = $this->getUserFromRole(DelegheRoles::RP);
if (isset($this->oggetto) && method_exists($this->oggetto, "getRup")) {
$rup_cf = $this->oggetto->getRup()["codice_fiscale"] ?? "";
} else {
$rup_cf = "";
}
$return = "Il codice fiscale del Responsabile Unico di Procedura (RUP) {$rup_cf} (impostato sul pannello Incarichi)
non corrisponde al codice fiscale del Responsabile Procedura (RP) {$rp_cf} (impostato sui Permessi ANAC).
{{STEP}}
- Se invece si tratta di un errore, assicurarsi di impostare il Codice Fiscale corretto sul pannello Incarichi.
Il modulo ANAC resterà disattivo fino alla risoluzione dell'incongruenza.";
if ($this->canClearDossier()) {
$step = <<
Possibili cause: