38 lines
4.5 KiB
PHP
38 lines
4.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
function requireContract(bool $condition, string $message): void { if (!$condition) { throw new RuntimeException($message); } }
|
|
$root = dirname(__DIR__);
|
|
$routes = file_get_contents($root . '/app/adminapi/route/app.php');
|
|
$controller = file_get_contents($root . '/app/adminapi/controller/IamV2Controller.php');
|
|
$source = file_get_contents($root . '/app/adminapi/service/iam/v2/IamV2Source.php');
|
|
$client = file_get_contents($root . '/app/adminapi/service/iam/v2/IamV2Client.php');
|
|
$config = file_get_contents($root . '/config/iam.php');
|
|
foreach (['proofs/introspect','sessions/check','events/revocation','operations','batch/preflight','batch','launch/start','self-link/start','password-setup','launch/prepare','launch/callback','launch/exchange','launch/reset'] as $route) {
|
|
requireContract(str_contains($routes,"Route::post('iam/v2/".$route."'"), 'POST registration ' . $route);
|
|
}
|
|
requireContract(str_contains($controller, "METHOD_NOT_ALLOWED"), 'default auto-route verbs checked');
|
|
requireContract(str_contains($controller,"hash_equals('Bearer ' . \$expected"), 'source service secret constant-time');
|
|
requireContract(str_contains($controller,'INVALID_ORIGIN'), 'browser mutation origin');
|
|
requireContract(str_contains($source,"self::check(\$p['sessionId']"), 'proof checks live session');
|
|
requireContract(str_contains($source,"self::authorize(\$session, substr(\$cap, 4)"), 'proof rechecks live permission');
|
|
requireContract(str_contains($source,"self::scope((int) \$session['admin_id'], \$targets)"), 'target data scope');
|
|
requireContract(str_contains($client,'CURLOPT_FOLLOWLOCATION => false') && str_contains($client,'CURLOPT_SSL_VERIFYPEER => true'), 'TLS fixed host no redirect');
|
|
requireContract(str_contains($config,"'v2_enabled' => env('iam.v2_enabled', false)") && str_contains($config,"'v2_management_enabled' => env('iam.v2_management_enabled', false)") && str_contains($config,"'v2_launch_enabled' => env('iam.v2_launch_enabled', false)"), 'global and new-flow gates default OFF');
|
|
requireContract(!str_contains($controller,"'token' =>"), 'no business token callback');
|
|
requireContract(str_contains($controller,"verifiedIdToken()") && str_contains($controller,"'oidcNonce' =>"), 'self-link independently verifiable OIDC token and nonce');
|
|
requireContract(!str_contains($controller,"request->post('employeeId'"), 'no browser chosen personal employee');
|
|
$callback = substr($controller, strpos($controller, 'public function checkSession()'), strpos($controller, 'public function launchStart()') - strpos($controller, 'public function checkSession()'));
|
|
requireContract(str_contains($callback, 'IamV2Source::liveStatus') && str_contains($source, "'sessionId' => \$s['session_id']") && str_contains($source, "'sessionVersion' => (int) \$s['version']"), 'live callback echoes actual SID/version');
|
|
requireContract(!str_contains($callback, 'IamV2Manager') && !str_contains($callback, 'IamV2Client'), 'source callback never recursively calls central under state lock');
|
|
$login = file_get_contents($root . '/app/adminapi/service/iam/IamLoginService.php');
|
|
requireContract(strpos($login, '->assertLinked(') < strpos($login, '$result = (array) (new LoginLogic())->login'), 'binding sync precedes new token issuance');
|
|
$provisioner = file_get_contents($root . '/app/adminapi/service/iam/IamAccountProvisioner.php');
|
|
requireContract(strpos($provisioner, 'return IamLoginService::boundAdminId') < strpos($provisioner, 'IAM_BINDING_REVOKED'), 'explicit central binding precedes local auto-create tombstone');
|
|
requireContract(str_contains($controller, "\$this->fail(\$code, ['error' => ['code' => \$code]], 0, 0)"), 'IAM structured errors suppress shared toast without changing other interfaces');
|
|
foreach (['launchContinue' => 'private function selfConfig', 'passwordContinue' => 'private function targetOrigin'] as $method => $next) {
|
|
$section = substr($controller, strpos($controller, 'public function ' . $method), strpos($controller, $next) - strpos($controller, 'public function ' . $method));
|
|
requireContract(str_contains($section, 'IamV2Flow::form(') && str_contains($section, "'Referrer-Policy' => 'strict-origin'"), 'cross-origin source form must retain non-null Origin: ' . $method);
|
|
}
|
|
requireContract(str_contains(file_get_contents($root . '/app/adminapi/service/iam/v2/IamV2Target.php'), "if (\$origin !== \$expectedOrigin)"), 'target Origin matching stays strict');
|
|
echo "PASS IamV2RouteContractTest: fixed POST callbacks, auto-route method gate, origin/service authentication, live proof authorization, target scope, dedicated TLS client, default-off, personal OIDC proof\n";
|