oauth2: flux automat token ANAF (FR-1..FR-11) + arhivare fisiere vechi

- index.php: schimb code->token cu tratare completa erori, sesiune
  PHP pentru legare state (CSRF), stocare atomica tokenuri in fisier
- pick.php: endpoint polling pentru ROACONT (preluare automata token)
- oauth2_tokens mutat in oauth2/tokens/ + .htaccess Require all denied
  (locatia initiala "extra-docroot" era de fapt in interiorul
  public_html, deci web-accesibila fara aceasta protectie)
- index1.php/index2.php/info.php (versiuni vechi) arhivate in
  _arhiva_oauth2/ cu .htaccess care blocheaza accesul HTTP

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 21:10:32 +03:00
parent 6afd3840ec
commit 1b94624a6d
7 changed files with 716 additions and 403 deletions

62
oauth2/pick.php Normal file
View File

@@ -0,0 +1,62 @@
<?php
// pick.php — Endpoint polling preluare automata token OAuth2 ANAF eFactura
// Apelat de ROACONT cu POST state=<64 hex>; raspunde JSON:
// {"status":"pending"} cat timp tokenul nu a sosit (intotdeauna HTTP 200, nu 404),
// continutul fisierului de token (succes sau eroare ANAF) o SINGURA data, apoi il sterge.
// Fara session_start() — evita serializarea pe lock-ul de sesiune PHP.
header('Content-Type: application/json; charset=utf-8');
// Director tokenuri (acelasi ca in index.php) — protejat cu .htaccess
$tokens_dir = __DIR__ . '/tokens';
if (!is_dir($tokens_dir)) {
@mkdir($tokens_dir, 0700, true);
}
$tokens_htaccess = $tokens_dir . '/.htaccess';
if (!file_exists($tokens_htaccess)) {
@file_put_contents($tokens_htaccess, "Require all denied\n");
}
// Validare stricta format state: 64 caractere [A-Za-z0-9] (protectie path traversal)
$state = isset($_POST['state']) ? (string)$_POST['state'] : '';
if (strlen($state) !== 64 || !preg_match('/^[A-Za-z0-9]+$/', $state)) {
http_response_code(400);
echo json_encode(['error' => 'invalid_state']);
exit();
}
// TTL: sterge fisierele de tokenuri (si .tmp orfane) mai vechi de 10 minute
// (aliniat cu timeout-ul de polling din ROACONT)
$now = time();
foreach (array_merge((array)glob($tokens_dir . '/*.json'), (array)glob($tokens_dir . '/*.tmp')) as $f) {
if (($now - filemtime($f)) > 600) {
@unlink($f);
}
}
// Numele fisierului = SHA-256(state): secretul nu apare in listinguri de directoare
$token_file = $tokens_dir . '/' . hash('sha256', $state) . '.json';
if (!file_exists($token_file)) {
echo json_encode(['status' => 'pending']);
exit();
}
// Claim atomic prin rename: din doua cereri concurente una singura primeste tokenul,
// cealalta "pending"; previne si citirea unui JSON partial (index.php scrie .tmp + rename)
$claim_file = $tokens_dir . '/' . hash('sha256', $state) . '_claim_' . getmypid() . '.json';
if (!@rename($token_file, $claim_file)) {
echo json_encode(['status' => 'pending']);
exit();
}
// Citire INAINTE de stergere; la esec nu stergem (TTL curata fisierul claim ramas)
$content = file_get_contents($claim_file);
if ($content === false || $content === '') {
echo json_encode(['error' => 'read_error']);
exit();
}
@unlink($claim_file);
// Continutul JSON exact cum a fost salvat de index.php (tokenuri sau eroare ANAF)
echo $content;