142 lines
8.5 KiB
PHP
142 lines
8.5 KiB
PHP
<?php
|
|
|
|
use App\Models\User;
|
|
use App\Modules\Identity\Services\IdentityWorkflow;
|
|
use App\Modules\Identity\Services\Permissions;
|
|
use App\Modules\Purchasing\BudgetAdapter;
|
|
use App\Shared\Context\CompanyContext;
|
|
use Illuminate\Contracts\Console\Kernel;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Session\ArraySessionHandler;
|
|
use Illuminate\Session\Store;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
use Tempo\Purchasing\Delivery\InitialAuthority;
|
|
use Tempo\Purchasing\SgfCandidate\Schema;
|
|
|
|
$host = getenv('IR_SGF_HOST');
|
|
$root = getenv('IR_CANDIDATE_ROOT');
|
|
$overlay = getenv('IR_ACCESS_OVERLAY');
|
|
require $host.'/vendor/autoload.php';
|
|
// Class replacements exist only in this isolated process, not in the installed host.
|
|
require $overlay.'/app/Modules/Identity/Services/Permissions.php';
|
|
require $overlay.'/app/Modules/Identity/Services/IdentityWorkflow.php';
|
|
require $overlay.'/AccessOverlay.php';
|
|
require $overlay.'/InitialAuthority.php';
|
|
$app = require $host.'/bootstrap/app.php';
|
|
$app->make(Kernel::class)->bootstrap();
|
|
BudgetAdapter::guard();
|
|
require $host.'/packages/budget-control/tests/sgf-schema.php';
|
|
require $host.'/packages/budget-control/tests/host-support.php';
|
|
$f = budgetQaFixture('sgf');
|
|
require $root.'/integrations/sgf/internal-requisitions/Schema.php';
|
|
Schema::install(DB::connection());
|
|
$scope = $f['scope'];
|
|
$actor = $f['actor'];
|
|
$target = $f['validator'];
|
|
$checks = [];
|
|
$check = function (bool $ok, string $name) use (&$checks) {
|
|
if (! $ok) {
|
|
throw new RuntimeException($name);
|
|
}
|
|
$checks[] = $name;
|
|
};
|
|
$deny = function (callable $work, int $status) use ($check) {
|
|
try {
|
|
$work();
|
|
throw new RuntimeException('Unexpected permission success');
|
|
} catch (HttpException $e) {
|
|
$check($e->getStatusCode() === $status, 'denied '.$status);
|
|
}
|
|
};
|
|
$grant = function ($user, $right, $unit = null) use ($scope) {
|
|
DB::table('permission_grants')->insertOrIgnore(['tenant_id' => $scope['tenant_id'], 'company_id' => $scope['company_id'], 'unit_id' => $unit, 'user_id' => $user, 'permission' => $right, 'created_at' => now(), 'updated_at' => now()]);
|
|
};
|
|
$ctx = app(CompanyContext::class);
|
|
$ctx->selectFor($actor, $scope['tenant_id'], $scope['company_id'], $scope['unit_id']);
|
|
$request = function (array $data) use ($actor) {
|
|
$r = Request::create('/api/access', 'POST', $data);
|
|
$r->setUserResolver(fn () => User::findOrFail($actor));
|
|
$session = new Store('access-proof', new ArraySessionHandler(120));
|
|
$session->start();
|
|
$session->put('company_context.token', 'isolated-context-token');
|
|
$r->setLaravelSession($session);
|
|
$r->headers->set('X-SGF-Context', 'isolated-context-token');
|
|
|
|
return $r;
|
|
};
|
|
$p = app(Permissions::class);
|
|
$workflow = app(IdentityWorkflow::class);
|
|
$grant($actor, 'access.manage');
|
|
$grant($actor, 'unit.create');
|
|
$grant($target, 'access.manage');
|
|
$check(in_array('purchasing_requisitions.configure', $workflow->accessList($request([]))['catalogue']['unit'], true), 'existing administration lists new catalogue');
|
|
$unit = $workflow->createUnit($request(['name' => 'Synthetic new unit']));
|
|
$check(DB::table('permission_grants')->where('unit_id', $unit)->count() === 2, 'unit creation grants only legacy rights');
|
|
$initial = ['operation_key' => (string) Str::uuid(), 'reason' => 'Entrega inicial sintética explicitamente escolhida.', 'administrators' => [['user_id' => $actor, 'rights' => ['purchasing_requisitions.view', 'purchasing_requisitions.configure']]]];
|
|
$service = new InitialAuthority;
|
|
$deny(fn () => $service->initialise($request($initial)), 403);
|
|
config(['purchasing-access.initialisation_scopes' => [implode(':', $scope)]]);
|
|
$stale = $request($initial);
|
|
$stale->headers->set('X-SGF-Context', 'old-token');
|
|
$deny(fn () => $service->initialise($stale), 409);
|
|
$options = $service->options($request([]));
|
|
$check(count($options['administrators']) === 2 && ! $options['initialised'], 'nominative authorised initialisation choices');
|
|
$render = function (array $options, Request $request) use ($overlay) {
|
|
ob_start();
|
|
require $overlay.'/screen.php';
|
|
|
|
return ob_get_clean();
|
|
};
|
|
$html = $render($options, $request([]));
|
|
$check(str_contains($html, 'name="rights"') && str_contains($html, 'X-SGF-Context') && str_contains($html, 'X-CSRF-TOKEN'), 'initial authority screen selects explicit rights and includes context and CSRF');
|
|
$check(! str_contains($html, ' checked') && str_contains($html, 'crypto.randomUUID()'), 'screen has no selected grants and retains operation key on retry');
|
|
$deny(fn () => $service->initialise($request([...$initial, 'administrators' => [['user_id' => $actor, 'rights' => ['company.edit']]]])), 422);
|
|
$failAudit = true;
|
|
DB::listen(function ($query) use (&$failAudit) {
|
|
if ($failAudit && str_contains($query->sql, 'insert into "identity_audit"') && in_array('purchasing.access_initialised', $query->bindings, true)) {
|
|
$failAudit = false;
|
|
throw new RuntimeException('Injected authority audit failure');
|
|
}
|
|
});
|
|
try {
|
|
$service->initialise($request($initial));
|
|
throw new RuntimeException('Expected audit failure');
|
|
} catch (RuntimeException $error) {
|
|
if ($error->getMessage() !== 'Injected authority audit failure') {
|
|
throw $error;
|
|
}
|
|
}
|
|
$check(! $p->has($actor, $scope['tenant_id'], $scope['company_id'], 'purchasing_requisitions.configure', $scope['unit_id']), 'audit failure rolls back initial grants');
|
|
$before = DB::table('identity_audit')->count();
|
|
$check($service->initialise($request($initial))['initialised'], 'explicit nominated administrator initialisation');
|
|
$check(DB::table('identity_audit')->count() === $before + 1, 'initial authority audited');
|
|
$check(! $p->has($actor, $scope['tenant_id'], $scope['company_id'], 'purchasing_requisitions.approve.finance', $scope['unit_id']), 'unselected rights absent');
|
|
$check($service->initialise($request($initial))['replayed'], 'initialisation retry idempotent');
|
|
$options = $service->options($request([]));
|
|
$check($options['initialised'] && ! str_contains($render($options, $request([])), '<form'), 'initialised screen cannot offer a second initial grant');
|
|
$change = ['email' => User::findOrFail($target)->email, 'permission' => 'purchasing_requisitions.configure', 'unit_id' => $scope['unit_id'], 'reason' => 'Delegação sintética explícita.'];
|
|
$workflow->changeAccess($request($change), true);
|
|
$check($p->has($target, $scope['tenant_id'], $scope['company_id'], $change['permission'], $scope['unit_id']), 'normal administrative grant');
|
|
$workflow->changeAccess($request($change), false);
|
|
$check(! $p->has($target, $scope['tenant_id'], $scope['company_id'], $change['permission'], $scope['unit_id']), 'normal administrative revocation');
|
|
$deny(fn () => $workflow->changeAccess($request([...$change, 'permission' => 'purchasing_requisitions.approve.finance']), true), 403);
|
|
$grant($target, 'purchasing_requisitions.approve.finance', $scope['unit_id']);
|
|
$deny(fn () => $workflow->changeAccess($request([...$change, 'permission' => 'unit.view']), false), 403);
|
|
$check($p->has($target, $scope['tenant_id'], $scope['company_id'], 'unit.view', $scope['unit_id']), 'failed cascade preserves unit access');
|
|
$grant($actor, 'purchasing_requisitions.approve.finance', $scope['unit_id']);
|
|
// Fixture also assigned budget rights; actor needs each affected right to revoke the full unit.
|
|
foreach (DB::table('permission_grants')->where($scope)->where('user_id', $target)->pluck('permission') as $right) {
|
|
$grant($actor, $right, $scope['unit_id']);
|
|
}
|
|
$workflow->changeAccess($request([...$change, 'permission' => 'unit.view']), false);
|
|
$check(DB::table('permission_grants')->where($scope)->where('user_id', $target)->count() === 0, 'unit revocation removes derived catalogue rights');
|
|
DB::table('permission_grants')->where($scope)->where('user_id', $actor)->where('permission', 'purchasing_requisitions.configure')->delete();
|
|
$check($service->initialise($request($initial))['replayed'] && ! $p->has($actor, $scope['tenant_id'], $scope['company_id'], 'purchasing_requisitions.configure', $scope['unit_id']), 'initialisation replay never resurrects revoked rights');
|
|
$deny(fn () => $service->initialise($request([...$initial, 'operation_key' => (string) Str::uuid()])), 409);
|
|
DB::table('permission_grants')->where('tenant_id', $scope['tenant_id'])->where('company_id', $scope['company_id'])->where('user_id', $actor)->where('permission', 'access.manage')->delete();
|
|
$deny(fn () => $service->initialise($request($initial)), 403);
|
|
$deny(fn () => $service->options($request([])), 403);
|
|
echo json_encode(['passed' => true, 'checks' => $checks, 'installed' => false], JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
|