- Entra admin roles scan job (ScanEntraAdminRolesJob) - Report service with fingerprint deduplication - Finding generator with high-privilege role catalog - Admin roles summary widget on tenant view page - Alert integration for entra.admin_roles findings - Graph contracts for roleDefinitions + roleAssignments - Entra permissions registry (config/entra_permissions.php) - StoredReport fingerprint migration - OperationCatalog label + duration for entra.admin_roles.scan - SummaryCountsNormalizer: filter zeros, humanize keys globally - 11 new test files (71+ tests, 286+ assertions) - Spec + tasks + checklist updates
141 lines
5.1 KiB
PHP
141 lines
5.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\StoredReport;
|
|
use App\Models\Tenant;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function createEntraReport(Tenant $tenant, array $summaryOverrides = [], ?string $fingerprint = null): StoredReport
|
|
{
|
|
$totals = array_merge([
|
|
'roles_total' => 8,
|
|
'assignments_total' => 12,
|
|
'high_privilege_assignments' => 5,
|
|
], $summaryOverrides);
|
|
|
|
return StoredReport::query()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'report_type' => StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES,
|
|
'fingerprint' => $fingerprint ?? hash('sha256', (string) now()->timestamp.random_int(0, 99999)),
|
|
'payload' => [
|
|
'provider_key' => 'microsoft',
|
|
'domain' => 'entra.admin_roles',
|
|
'measured_at' => now()->toIso8601String(),
|
|
'role_definitions' => [],
|
|
'role_assignments' => [],
|
|
'totals' => $totals,
|
|
'high_privilege' => [
|
|
[
|
|
'role_display_name' => 'Global Administrator',
|
|
'role_template_id' => '62e90394-69f5-4237-9190-012177145e10',
|
|
'principal_id' => 'user-ga-1',
|
|
'principal_display_name' => 'Admin User',
|
|
'assignment_scope' => '/',
|
|
'severity' => 'critical',
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// StoredReport query by report type
|
|
// ---------------------------------------------------------------------------
|
|
|
|
it('returns stored reports with report_type entra.admin_roles', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
$report = createEntraReport($tenant);
|
|
|
|
// Also create a report of a different type to ensure filtering works
|
|
StoredReport::query()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'report_type' => 'permission_posture',
|
|
'fingerprint' => hash('sha256', 'other-report'),
|
|
'payload' => ['summary' => ['score' => 85]],
|
|
]);
|
|
|
|
$results = StoredReport::query()
|
|
->where('tenant_id', (int) $tenant->getKey())
|
|
->where('report_type', StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES)
|
|
->get();
|
|
|
|
expect($results)
|
|
->toHaveCount(1)
|
|
->and((int) $results->first()->getKey())->toBe((int) $report->getKey());
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Report payload contains summary and high-privilege assignments table
|
|
// ---------------------------------------------------------------------------
|
|
|
|
it('report payload contains summary totals and high-privilege assignments', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
$report = createEntraReport($tenant, [
|
|
'roles_total' => 10,
|
|
'assignments_total' => 20,
|
|
'high_privilege_assignments' => 8,
|
|
]);
|
|
|
|
$report->refresh();
|
|
$payload = $report->payload;
|
|
|
|
expect($payload)
|
|
->toBeArray()
|
|
->toHaveKey('totals')
|
|
->toHaveKey('high_privilege');
|
|
|
|
$totals = $payload['totals'];
|
|
expect($totals)
|
|
->toHaveKey('roles_total', 10)
|
|
->toHaveKey('assignments_total', 20)
|
|
->toHaveKey('high_privilege_assignments', 8);
|
|
|
|
$assignments = $payload['high_privilege'];
|
|
expect($assignments)
|
|
->toBeArray()
|
|
->toHaveCount(1);
|
|
|
|
expect($assignments[0])
|
|
->toHaveKey('role_display_name', 'Global Administrator')
|
|
->toHaveKey('severity', 'critical')
|
|
->toHaveKey('principal_display_name', 'Admin User');
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Multiple reports ordered by creation date descending
|
|
// ---------------------------------------------------------------------------
|
|
|
|
it('orders multiple reports by creation date descending', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
$older = createEntraReport($tenant, ['high_privilege_assignments' => 3], 'fp-older');
|
|
$older->forceFill(['created_at' => now()->subHours(2)])->save();
|
|
|
|
$newer = createEntraReport($tenant, ['high_privilege_assignments' => 7], 'fp-newer');
|
|
$newer->forceFill(['created_at' => now()->subHour()])->save();
|
|
|
|
$latest = createEntraReport($tenant, ['high_privilege_assignments' => 1], 'fp-latest');
|
|
|
|
$results = StoredReport::query()
|
|
->where('tenant_id', (int) $tenant->getKey())
|
|
->where('report_type', StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES)
|
|
->orderByDesc('created_at')
|
|
->get();
|
|
|
|
expect($results)->toHaveCount(3);
|
|
expect((int) $results[0]->getKey())->toBe((int) $latest->getKey());
|
|
expect((int) $results[1]->getKey())->toBe((int) $newer->getKey());
|
|
expect((int) $results[2]->getKey())->toBe((int) $older->getKey());
|
|
});
|