Compare commits
4 Commits
1c80ab98fc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9fc17765f6 | |||
| 80d080e2d6 | |||
| b416bf5061 | |||
| becbbe2be4 |
18
CLAUDE.md
18
CLAUDE.md
@@ -34,6 +34,23 @@ Directorul `efactura-generator/` din acest repo este o **oglindă** a proiectulu
|
|||||||
|
|
||||||
`config.json` de pe server (`~/public_html/efactura-generator/config.json`) conține `api_key` și nu e nici în repo, nici în sync — se gestionează manual pe a2hosting.
|
`config.json` de pe server (`~/public_html/efactura-generator/config.json`) conține `api_key` și nu e nici în repo, nici în sync — se gestionează manual pe a2hosting.
|
||||||
|
|
||||||
|
## Sub-proiectul `oauth2/`
|
||||||
|
|
||||||
|
Flux OAuth2 ANAF eFactura (obținere access/refresh token) folosit de aplicația desktop ROA/ROACONT. Deployat pe `https://romfast.ro/oauth2/`.
|
||||||
|
|
||||||
|
- **`index.php`** — endpoint principal, trei ramuri:
|
||||||
|
1. **Refresh token** (`refresh_token` primit prin `$_POST` sau `$_GET`) → passthrough curl către ANAF, întoarce JSON brut (folosit de butonul "Actualizare" din ROA).
|
||||||
|
2. **Intrare inițială** (fără `?code`) → redirect 302 către ANAF authorize. Dacă vine cu `?state=<64 hex>` salvează state-ul în sesiunea PHP (flux automat, legat de `pick.php`); fără `state` = flux manual/retro-compat (tokenurile apar direct în pagină).
|
||||||
|
3. **Callback ANAF** (`?code=...` sau `?error=...`) → schimb code→token; dacă există state valid în sesiune scrie fișier în `tokens/<sha256(state)>.json` (succes sau eroare) și afișează doar o pagină de confirmare; altfel (retro-compat) afișează access/refresh token direct în HTML.
|
||||||
|
- **`pick.php`** — endpoint de polling apelat de ROA cu `POST state=<64 hex>`. Răspunde `{"status":"pending"}` până apare fișierul de token, apoi îl livrează o singură dată (claim atomic prin `rename`) și îl șterge. TTL 600s pentru fișiere orfane/`.tmp`.
|
||||||
|
- **`tokens/`** — director de stocare temporară token/eroare, cheie = SHA-256(state). Protejat cu `.htaccess` (`Require all denied`); creat automat de `index.php`/`pick.php` dacă lipsește. Nu se commite conținutul (fișiere efemere, curățate de TTL).
|
||||||
|
- **`php.ini`** — config logging erori local pentru acest subdirector (`error_log = /oauth2/error_log.txt`).
|
||||||
|
- **`tests/run_tests.php`** — suită de teste self-contained (fără framework extern), acoperă `index.php` + `pick.php`. Rulare:
|
||||||
|
```bash
|
||||||
|
php oauth2/tests/run_tests.php
|
||||||
|
```
|
||||||
|
Ce face: copiază `index.php`/`pick.php` într-un director temporar (nu atinge `tokens/` din repo), pornește `php -S 127.0.0.1:8317` pe copie cu `session.save_path` propriu, rulează matricea de teste prin cereri HTTP reale (fără dependențe externe), oprește serverul și curăță totul la final (`register_shutdown_function`). Exit code 0 = toate testele au trecut; ieșirea arată `OK`/`FAIL` per test plus un rezumat `N trecute, M eșuate`. Notă: testele de refresh declanșează un apel curl real către `logincert.anaf.ro` cu un refresh_token fictiv (ANAF răspunde `invalid_grant`) — asercțiunile (nu e 302, `Content-Type` JSON) rămân valabile și offline.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
No build process. Serve locally with:
|
No build process. Serve locally with:
|
||||||
@@ -85,6 +102,7 @@ The navbar HTML is **manually duplicated** across every page (no templating). Wh
|
|||||||
- `/menu/` — company pages (about, contact, support, services, jobs, references)
|
- `/menu/` — company pages (about, contact, support, services, jobs, references)
|
||||||
- `/roa/` — ROA ERP product pages (one per module)
|
- `/roa/` — ROA ERP product pages (one per module)
|
||||||
- `/efactura-generator/` — standalone Romanian e-invoice editor (loads XML, edits, prints, exports XML)
|
- `/efactura-generator/` — standalone Romanian e-invoice editor (loads XML, edits, prints, exports XML)
|
||||||
|
- `/oauth2/` — ANAF eFactura OAuth2 token flow for the ROA desktop app (see "Sub-proiectul `oauth2/`" above)
|
||||||
|
|
||||||
### efactura-generator
|
### efactura-generator
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,13 @@
|
|||||||
<span class="link-arrow mt-4 inline-block">Deschide aplicația →</span>
|
<span class="link-arrow mt-4 inline-block">Deschide aplicația →</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href="https://autopass.romfast.ro" target="_blank" rel="noopener" class="professional-card" style="padding: 26px 24px;">
|
||||||
|
<span class="feature-icon"><i data-lucide="car" class="w-5 h-5" style="color:var(--primary)"></i></span>
|
||||||
|
<span class="feature-title mt-4 inline-block">ROA AutoPass</span>
|
||||||
|
<p class="feature-desc mt-2">Declari prestațiile de service auto la RAR din câteva click-uri — încarci Excel/CSV, restul se face automat.</p>
|
||||||
|
<span class="link-arrow mt-4 inline-block">Deschide aplicația →</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="hero-sub mx-auto text-center mt-10" style="font-size:15px;">
|
<p class="hero-sub mx-auto text-center mt-10" style="font-size:15px;">
|
||||||
@@ -171,6 +178,7 @@
|
|||||||
<a href="https://pdfqr.roa.romfast.ro" target="_blank" rel="noopener" title="Generează cod QR și-l adaugă la PDF" class="inline-flex items-center gap-1.5 text-sm"><i data-lucide="scan-line" class="w-4 h-4"></i>PDF QR</a>
|
<a href="https://pdfqr.roa.romfast.ro" target="_blank" rel="noopener" title="Generează cod QR și-l adaugă la PDF" class="inline-flex items-center gap-1.5 text-sm"><i data-lucide="scan-line" class="w-4 h-4"></i>PDF QR</a>
|
||||||
<a href="https://city-discovery.roa.romfast.ro" target="_blank" rel="noopener" title="Joc City Adventure Game" class="inline-flex items-center gap-1.5 text-sm"><i data-lucide="gamepad-2" class="w-4 h-4"></i>City Discovery</a>
|
<a href="https://city-discovery.roa.romfast.ro" target="_blank" rel="noopener" title="Joc City Adventure Game" class="inline-flex items-center gap-1.5 text-sm"><i data-lucide="gamepad-2" class="w-4 h-4"></i>City Discovery</a>
|
||||||
<a href="https://space.roa.romfast.ro" target="_blank" rel="noopener" title="Rezervări spații" class="inline-flex items-center gap-1.5 text-sm"><i data-lucide="calendar-check" class="w-4 h-4"></i>Space Booking</a>
|
<a href="https://space.roa.romfast.ro" target="_blank" rel="noopener" title="Rezervări spații" class="inline-flex items-center gap-1.5 text-sm"><i data-lucide="calendar-check" class="w-4 h-4"></i>Space Booking</a>
|
||||||
|
<a href="https://autopass.romfast.ro" target="_blank" rel="noopener" title="Declarații RAR pentru service-uri auto" class="inline-flex items-center gap-1.5 text-sm"><i data-lucide="car" class="w-4 h-4"></i>ROA AutoPass</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,10 @@ function showPage($page_title, $h1_text, $h1_color, $body_text) {
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
// Citire explicita parametri GET (FR-10: elimina parse_str care injecta variabile)
|
// Citire explicita parametri GET (FR-10: elimina parse_str care injecta variabile)
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
$refresh_token = isset($_GET['refresh_token']) ? (string)$_GET['refresh_token'] : '';
|
// refresh_token vine din ROA prin POST body (refreshToken - anaf_efactura.prg) sau GET;
|
||||||
|
// originalul folosea $_REQUEST, deci accepta ambele - citirea doar din $_GET rupe butonul Actualizare
|
||||||
|
$refresh_token = isset($_POST['refresh_token']) ? (string)$_POST['refresh_token']
|
||||||
|
: (isset($_GET['refresh_token']) ? (string)$_GET['refresh_token'] : '');
|
||||||
$code = isset($_GET['code']) ? (string)$_GET['code'] : '';
|
$code = isset($_GET['code']) ? (string)$_GET['code'] : '';
|
||||||
$state_param = isset($_GET['state']) ? (string)$_GET['state'] : '';
|
$state_param = isset($_GET['state']) ? (string)$_GET['state'] : '';
|
||||||
$error_param = isset($_GET['error']) ? (string)$_GET['error'] : '';
|
$error_param = isset($_GET['error']) ? (string)$_GET['error'] : '';
|
||||||
|
|||||||
322
oauth2/tests/run_tests.php
Normal file
322
oauth2/tests/run_tests.php
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
<?php
|
||||||
|
// run_tests.php — teste pentru oauth2/index.php + oauth2/pick.php (flux automat token ANAF)
|
||||||
|
//
|
||||||
|
// Rulare: php oauth2/tests/run_tests.php
|
||||||
|
//
|
||||||
|
// Nu exista framework de teste in proiect; scriptul este self-contained:
|
||||||
|
// - copiaza index.php si pick.php intr-un director temporar (nu atinge tokens/ din repo),
|
||||||
|
// - porneste "php -S 127.0.0.1:8317" pe copie, cu sesiuni intr-un save_path propriu,
|
||||||
|
// - ruleaza matricea de teste cu cereri HTTP (streams, fara dependinte externe),
|
||||||
|
// - opreste serverul si curata directorul temporar; exit code 0 = toate au trecut.
|
||||||
|
//
|
||||||
|
// NOTA retea: testele de refresh (passthrough) declanseaza in index.php un apel curl
|
||||||
|
// real catre logincert.anaf.ro cu un refresh_token fictiv (ANAF raspunde invalid_grant).
|
||||||
|
// Asertiunile (nu e 302, Content-Type JSON) raman valabile si offline (corp gol).
|
||||||
|
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
const BASE = 'http://127.0.0.1:8317';
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// Helper HTTP: cerere fara follow-redirect, cu cookie optional
|
||||||
|
// Returneaza ['status' => int, 'headers' => array, 'body' => string]
|
||||||
|
// ===================================================================
|
||||||
|
function http($method, $url, $post = null, $cookie = null, $timeout = 45) {
|
||||||
|
$header = "Connection: close\r\n";
|
||||||
|
if ($cookie !== null) {
|
||||||
|
$header .= 'Cookie: ' . $cookie . "\r\n";
|
||||||
|
}
|
||||||
|
$opts = ['http' => [
|
||||||
|
'method' => $method,
|
||||||
|
'ignore_errors' => true, // returneaza corpul si la 4xx/5xx
|
||||||
|
'follow_location' => 0, // vrem sa vedem 302-ul, nu sa-l urmam
|
||||||
|
'timeout' => $timeout,
|
||||||
|
]];
|
||||||
|
if ($post !== null) {
|
||||||
|
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
|
||||||
|
$opts['http']['content'] = http_build_query($post);
|
||||||
|
}
|
||||||
|
$opts['http']['header'] = $header;
|
||||||
|
$body = @file_get_contents($url, false, stream_context_create($opts));
|
||||||
|
$headers = isset($http_response_header) ? $http_response_header : [];
|
||||||
|
$status = 0;
|
||||||
|
if (!empty($headers) && preg_match('#^HTTP/\S+\s+(\d{3})#', $headers[0], $m)) {
|
||||||
|
$status = (int)$m[1];
|
||||||
|
}
|
||||||
|
return ['status' => $status, 'headers' => $headers, 'body' => (string)$body];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cauta un header dupa nume (case-insensitive); null daca lipseste
|
||||||
|
function hdr($resp, $name) {
|
||||||
|
foreach ($resp['headers'] as $h) {
|
||||||
|
if (stripos($h, $name . ':') === 0) {
|
||||||
|
return trim(substr($h, strlen($name) + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// Mod copil pentru testul de claim concurent:
|
||||||
|
// php run_tests.php --pick-once <url> <state>
|
||||||
|
// Face un singur POST pe pick.php si scrie corpul raspunsului pe stdout.
|
||||||
|
// ===================================================================
|
||||||
|
if (isset($argv[1]) && $argv[1] === '--pick-once') {
|
||||||
|
$r = http('POST', $argv[2], ['state' => $argv[3]]);
|
||||||
|
echo $r['body'];
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// Setup: director temporar + server php -S
|
||||||
|
// ===================================================================
|
||||||
|
$tmp = rtrim(sys_get_temp_dir(), '/\\') . DIRECTORY_SEPARATOR . 'roa_oauth2_tests_' . getmypid();
|
||||||
|
$sessions = $tmp . DIRECTORY_SEPARATOR . 'sessions';
|
||||||
|
$tokens = $tmp . DIRECTORY_SEPARATOR . 'tokens';
|
||||||
|
if (!mkdir($tmp, 0700, true) || !mkdir($sessions, 0700, true)) {
|
||||||
|
fwrite(STDERR, "Nu pot crea directorul temporar: $tmp\n");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
copy(__DIR__ . '/../index.php', $tmp . DIRECTORY_SEPARATOR . 'index.php');
|
||||||
|
copy(__DIR__ . '/../pick.php', $tmp . DIRECTORY_SEPARATOR . 'pick.php');
|
||||||
|
|
||||||
|
$server = proc_open(
|
||||||
|
[PHP_BINARY, '-S', '127.0.0.1:8317', '-t', $tmp,
|
||||||
|
'-d', 'session.save_path=' . $sessions,
|
||||||
|
'-d', 'xdebug.mode=off'],
|
||||||
|
[1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
|
||||||
|
$pipes
|
||||||
|
);
|
||||||
|
if (!is_resource($server)) {
|
||||||
|
fwrite(STDERR, "Nu pot porni php -S\n");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
register_shutdown_function(function () use ($server, $tmp) {
|
||||||
|
@proc_terminate($server);
|
||||||
|
@proc_close($server);
|
||||||
|
// curatare best-effort a directorului temporar
|
||||||
|
$it = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($tmp, FilesystemIterator::SKIP_DOTS),
|
||||||
|
RecursiveIteratorIterator::CHILD_FIRST
|
||||||
|
);
|
||||||
|
foreach ($it as $f) {
|
||||||
|
$f->isDir() ? @rmdir($f->getPathname()) : @unlink($f->getPathname());
|
||||||
|
}
|
||||||
|
@rmdir($tmp);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Asteapta serverul (max 5 secunde)
|
||||||
|
$ready = false;
|
||||||
|
for ($i = 0; $i < 50; $i++) {
|
||||||
|
$s = @fsockopen('127.0.0.1', 8317, $en, $es, 0.1);
|
||||||
|
if ($s) { fclose($s); $ready = true; break; }
|
||||||
|
usleep(100000);
|
||||||
|
}
|
||||||
|
if (!$ready) {
|
||||||
|
fwrite(STDERR, "Serverul php -S nu a pornit pe 127.0.0.1:8317\n");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// Asertiuni
|
||||||
|
// ===================================================================
|
||||||
|
$pass = 0;
|
||||||
|
$fail = 0;
|
||||||
|
function check($name, $cond, $detail = '') {
|
||||||
|
global $pass, $fail;
|
||||||
|
if ($cond) {
|
||||||
|
$pass++;
|
||||||
|
echo " OK $name\n";
|
||||||
|
} else {
|
||||||
|
$fail++;
|
||||||
|
echo " FAIL $name" . ($detail !== '' ? " [$detail]" : '') . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function state_nou() {
|
||||||
|
return bin2hex(random_bytes(32)); // 64 caractere hex = format valid
|
||||||
|
}
|
||||||
|
function fisier_token($tokens, $state) {
|
||||||
|
return $tokens . DIRECTORY_SEPARATOR . hash('sha256', $state) . '.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// pick.php
|
||||||
|
// ===================================================================
|
||||||
|
echo "pick.php\n";
|
||||||
|
|
||||||
|
// P1: fara state -> 400 invalid_state
|
||||||
|
$r = http('POST', BASE . '/pick.php', []);
|
||||||
|
check('P1 fara state -> 400 invalid_state',
|
||||||
|
$r['status'] === 400 && strpos($r['body'], 'invalid_state') !== false,
|
||||||
|
"status={$r['status']} body={$r['body']}");
|
||||||
|
|
||||||
|
// P2: state prea scurt -> 400
|
||||||
|
$r = http('POST', BASE . '/pick.php', ['state' => 'abc123']);
|
||||||
|
check('P2 state prea scurt -> 400', $r['status'] === 400, "status={$r['status']}");
|
||||||
|
|
||||||
|
// P3: lungime 64 dar caractere invalide (path traversal) -> 400
|
||||||
|
$r = http('POST', BASE . '/pick.php', ['state' => str_repeat('a', 60) . '/../']);
|
||||||
|
check('P3 caractere invalide -> 400', $r['status'] === 400, "status={$r['status']}");
|
||||||
|
|
||||||
|
// P4: state valid, fisier inexistent -> 200 pending
|
||||||
|
$s = state_nou();
|
||||||
|
$r = http('POST', BASE . '/pick.php', ['state' => $s]);
|
||||||
|
check('P4 fara fisier -> 200 {"status":"pending"}',
|
||||||
|
$r['status'] === 200 && $r['body'] === '{"status":"pending"}',
|
||||||
|
"status={$r['status']} body={$r['body']}");
|
||||||
|
check('P4 Content-Type JSON',
|
||||||
|
stripos((string)hdr($r, 'Content-Type'), 'application/json') !== false);
|
||||||
|
|
||||||
|
// FR-11: prima cerere a creat tokens/ + .htaccess "Require all denied"
|
||||||
|
check('FR-11 tokens/.htaccess creat automat',
|
||||||
|
is_file($tokens . '/.htaccess')
|
||||||
|
&& strpos((string)file_get_contents($tokens . '/.htaccess'), 'Require all denied') !== false);
|
||||||
|
|
||||||
|
// P5: fisier prezent -> continutul exact, O SINGURA data, apoi pending
|
||||||
|
$s = state_nou();
|
||||||
|
$continut = '{"access_token":"AT_test","refresh_token":"RT_test","expires_in":3600}';
|
||||||
|
file_put_contents(fisier_token($tokens, $s), $continut);
|
||||||
|
$r = http('POST', BASE . '/pick.php', ['state' => $s]);
|
||||||
|
check('P5 fisier prezent -> continutul exact', $r['body'] === $continut,
|
||||||
|
"body={$r['body']}");
|
||||||
|
check('P5 fisierul e sters dupa livrare', !file_exists(fisier_token($tokens, $s)));
|
||||||
|
$r = http('POST', BASE . '/pick.php', ['state' => $s]);
|
||||||
|
check('P5 a doua cerere -> pending', $r['body'] === '{"status":"pending"}',
|
||||||
|
"body={$r['body']}");
|
||||||
|
|
||||||
|
// P6: TTL — fisier mai vechi de 10 minute -> sters, raspuns pending
|
||||||
|
$s = state_nou();
|
||||||
|
file_put_contents(fisier_token($tokens, $s), $continut);
|
||||||
|
touch(fisier_token($tokens, $s), time() - 700);
|
||||||
|
$r = http('POST', BASE . '/pick.php', ['state' => $s]);
|
||||||
|
check('P6 fisier expirat (TTL 600s) -> pending', $r['body'] === '{"status":"pending"}',
|
||||||
|
"body={$r['body']}");
|
||||||
|
check('P6 fisierul expirat e sters', !file_exists(fisier_token($tokens, $s)));
|
||||||
|
|
||||||
|
// P7: TTL curata si .tmp orfane vechi
|
||||||
|
$tmp_orfan = $tokens . DIRECTORY_SEPARATOR . 'orfan.tmp';
|
||||||
|
file_put_contents($tmp_orfan, 'x');
|
||||||
|
touch($tmp_orfan, time() - 700);
|
||||||
|
http('POST', BASE . '/pick.php', ['state' => state_nou()]);
|
||||||
|
check('P7 .tmp orfan vechi e sters de TTL', !file_exists($tmp_orfan));
|
||||||
|
|
||||||
|
// P8: claim concurent — doua cereri simultane, exact una primeste tokenul.
|
||||||
|
// Nota: php -S pe Windows serveste secvential; concurenta reala e pe partea de
|
||||||
|
// client, dar mecanismul testat (rename atomic in pick.php) acopera ambele cazuri.
|
||||||
|
$s = state_nou();
|
||||||
|
file_put_contents(fisier_token($tokens, $s), $continut);
|
||||||
|
$copii = [];
|
||||||
|
$pipe = [];
|
||||||
|
for ($i = 0; $i < 2; $i++) {
|
||||||
|
$copii[$i] = proc_open(
|
||||||
|
[PHP_BINARY, '-d', 'xdebug.mode=off', __FILE__, '--pick-once', BASE . '/pick.php', $s],
|
||||||
|
[1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
|
||||||
|
$pipe[$i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$rasp = [];
|
||||||
|
for ($i = 0; $i < 2; $i++) {
|
||||||
|
$rasp[$i] = stream_get_contents($pipe[$i][1]);
|
||||||
|
proc_close($copii[$i]);
|
||||||
|
}
|
||||||
|
$cu_token = 0;
|
||||||
|
$cu_pending = 0;
|
||||||
|
foreach ($rasp as $b) {
|
||||||
|
if ($b === $continut) $cu_token++;
|
||||||
|
if ($b === '{"status":"pending"}') $cu_pending++;
|
||||||
|
}
|
||||||
|
check('P8 claim concurent: exact una primeste tokenul, cealalta pending',
|
||||||
|
$cu_token === 1 && $cu_pending === 1,
|
||||||
|
'raspunsuri: [' . implode('] [', $rasp) . ']');
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// index.php
|
||||||
|
// ===================================================================
|
||||||
|
echo "index.php\n";
|
||||||
|
|
||||||
|
// I1: fara parametri -> 302 catre authorize, FARA client_secret, fara state
|
||||||
|
$r = http('GET', BASE . '/index.php');
|
||||||
|
$loc = (string)hdr($r, 'Location');
|
||||||
|
check('I1 fara parametri -> 302', $r['status'] === 302, "status={$r['status']}");
|
||||||
|
check('I1 Location = ANAF authorize',
|
||||||
|
strpos($loc, 'https://logincert.anaf.ro/anaf-oauth2/v1/authorize?') === 0, "loc=$loc");
|
||||||
|
check('I1 authorize contine response_type=code + jwt',
|
||||||
|
strpos($loc, 'response_type=code') !== false
|
||||||
|
&& strpos($loc, 'token_content_type=jwt') !== false);
|
||||||
|
check('I1 client_secret NU apare in URL-ul authorize',
|
||||||
|
strpos($loc, 'client_secret') === false, "loc=$loc");
|
||||||
|
check('I1 fara state in URL cand nu s-a trimis state',
|
||||||
|
strpos($loc, 'state=') === false, "loc=$loc");
|
||||||
|
|
||||||
|
// I2: ?state=valid -> 302 cu state propagat + cookie de sesiune
|
||||||
|
$s = state_nou();
|
||||||
|
$r = http('GET', BASE . '/index.php?state=' . $s);
|
||||||
|
$loc = (string)hdr($r, 'Location');
|
||||||
|
check('I2 ?state=valid -> 302 spre authorize', $r['status'] === 302
|
||||||
|
&& strpos($loc, 'https://logincert.anaf.ro/anaf-oauth2/v1/authorize?') === 0,
|
||||||
|
"status={$r['status']}");
|
||||||
|
check('I2 state propagat in authorize', strpos($loc, 'state=' . $s) !== false, "loc=$loc");
|
||||||
|
check('I2 client_secret NU apare', strpos($loc, 'client_secret') === false);
|
||||||
|
$setc = (string)hdr($r, 'Set-Cookie');
|
||||||
|
check('I2 Set-Cookie sesiune PHP', strpos($setc, 'PHPSESSID=') !== false, "set-cookie=$setc");
|
||||||
|
|
||||||
|
// I3: ?state cu format invalid -> pagina de eroare, fara redirect
|
||||||
|
$r = http('GET', BASE . '/index.php?state=' . str_repeat('a', 60) . '..%2F.');
|
||||||
|
check('I3 state invalid -> 200 pagina eroare (nu redirect)',
|
||||||
|
$r['status'] === 200 && hdr($r, 'Location') === null, "status={$r['status']}");
|
||||||
|
check('I3 pagina contine "Autorizarea nu a reușit"',
|
||||||
|
strpos($r['body'], 'Autorizarea nu a reușit') !== false);
|
||||||
|
|
||||||
|
// I4: refresh prin POST body -> passthrough JSON (regresia fixata in becbbe2:
|
||||||
|
// inainte, POST-ul era ignorat si cererea cadea pe 302 authorize)
|
||||||
|
$r = http('POST', BASE . '/index.php', ['refresh_token' => 'dummy_refresh_token_test']);
|
||||||
|
check('I4 refresh POST -> nu e 302 (regresie buton Actualizare)',
|
||||||
|
$r['status'] !== 302, "status={$r['status']}");
|
||||||
|
check('I4 refresh POST -> Content-Type application/json',
|
||||||
|
stripos((string)hdr($r, 'Content-Type'), 'application/json') !== false,
|
||||||
|
'content-type=' . hdr($r, 'Content-Type'));
|
||||||
|
|
||||||
|
// I5: refresh prin GET (compatibilitate retro) -> acelasi passthrough
|
||||||
|
$r = http('GET', BASE . '/index.php?refresh_token=dummy_refresh_token_test');
|
||||||
|
check('I5 refresh GET -> nu e 302', $r['status'] !== 302, "status={$r['status']}");
|
||||||
|
check('I5 refresh GET -> Content-Type application/json',
|
||||||
|
stripos((string)hdr($r, 'Content-Type'), 'application/json') !== false,
|
||||||
|
'content-type=' . hdr($r, 'Content-Type'));
|
||||||
|
|
||||||
|
// I6: callback ?error= fara sesiune -> pagina de eroare, fara fisier scris
|
||||||
|
$inainte = count((array)glob($tokens . '/*.json'));
|
||||||
|
$r = http('GET', BASE . '/index.php?error=access_denied&error_description=Test');
|
||||||
|
check('I6 ?error= fara sesiune -> pagina eroare',
|
||||||
|
$r['status'] === 200 && strpos($r['body'], 'Autorizarea nu a reușit') !== false,
|
||||||
|
"status={$r['status']}");
|
||||||
|
check('I6 nu se scrie fisier de token fara sesiune',
|
||||||
|
count((array)glob($tokens . '/*.json')) === $inainte);
|
||||||
|
|
||||||
|
// I7: flux complet de eroare: intrare cu state (sesiune) -> callback ?error=
|
||||||
|
// -> fisier de eroare scris -> pick.php il livreaza o singura data
|
||||||
|
$s = state_nou();
|
||||||
|
$r = http('GET', BASE . '/index.php?state=' . $s);
|
||||||
|
$cookie = null;
|
||||||
|
if (preg_match('/PHPSESSID=([^;]+)/', (string)hdr($r, 'Set-Cookie'), $m)) {
|
||||||
|
$cookie = 'PHPSESSID=' . $m[1];
|
||||||
|
}
|
||||||
|
check('I7 intrare cu state -> cookie sesiune obtinut', $cookie !== null);
|
||||||
|
$r = http('GET', BASE . '/index.php?error=access_denied&error_description=Utilizatorul+a+refuzat',
|
||||||
|
null, $cookie);
|
||||||
|
check('I7 callback ?error= cu sesiune -> pagina eroare',
|
||||||
|
$r['status'] === 200 && strpos($r['body'], 'Autorizarea nu a reușit') !== false,
|
||||||
|
"status={$r['status']}");
|
||||||
|
$r = http('POST', BASE . '/pick.php', ['state' => $s]);
|
||||||
|
$j = json_decode($r['body'], true);
|
||||||
|
check('I7 pick.php livreaza eroarea ANAF',
|
||||||
|
is_array($j) && isset($j['error']) && $j['error'] === 'access_denied',
|
||||||
|
"body={$r['body']}");
|
||||||
|
$r = http('POST', BASE . '/pick.php', ['state' => $s]);
|
||||||
|
check('I7 a doua cerere pick -> pending', $r['body'] === '{"status":"pending"}',
|
||||||
|
"body={$r['body']}");
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// Rezumat
|
||||||
|
// ===================================================================
|
||||||
|
echo "\n$pass trecute, $fail esuate\n";
|
||||||
|
exit($fail === 0 ? 0 : 1);
|
||||||
Reference in New Issue
Block a user