## Summary - add an in-place Required Permissions assist to the onboarding Verify Access step via a Filament slideover - route permission-related verification remediation links into the assist first and keep deep-dive links opening in a new tab - add view-model and link-behavior helpers plus focused feature, browser, RBAC, and unit coverage for the new assist ## Scope - onboarding wizard Verify Access UX - Required Permissions assist rendering and link behavior - Spec 139 artifacts, contracts, and checklist updates ## Notes - branch: `139-verify-access-permissions-assist` - commit: `b4193f1` - worktree was clean at PR creation time Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #168
291 lines
10 KiB
PHP
291 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Workspaces\ManagedTenantOnboardingWizard;
|
|
use App\Filament\Resources\ProviderConnectionResource;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\Tenant;
|
|
use App\Models\TenantOnboardingSession;
|
|
use App\Models\User;
|
|
use App\Models\Workspace;
|
|
use App\Models\WorkspaceMembership;
|
|
use App\Support\Links\RequiredPermissionsLinks;
|
|
use App\Support\Verification\VerificationReportWriter;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Livewire\Livewire;
|
|
|
|
it('renders clustered verification checks issues-first in the onboarding wizard verify step', function (): void {
|
|
$workspace = Workspace::factory()->create();
|
|
$user = User::factory()->create();
|
|
|
|
WorkspaceMembership::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'user_id' => (int) $user->getKey(),
|
|
'role' => 'owner',
|
|
]);
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
|
|
|
|
$entraTenantId = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
|
|
$tenant = Tenant::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => $entraTenantId,
|
|
'external_id' => 'tenant-clusters-a',
|
|
'status' => 'onboarding',
|
|
]);
|
|
|
|
$user->tenants()->syncWithoutDetaching([
|
|
$tenant->getKey() => ['role' => 'owner'],
|
|
]);
|
|
|
|
$checks = [
|
|
[
|
|
'key' => 'provider.connection.check',
|
|
'title' => 'Provider connection check',
|
|
'status' => 'pass',
|
|
'severity' => 'info',
|
|
'blocking' => false,
|
|
'reason_code' => 'ok',
|
|
'message' => 'Connection is healthy.',
|
|
'evidence' => [],
|
|
'next_steps' => [],
|
|
],
|
|
[
|
|
'key' => 'permissions.admin_consent',
|
|
'title' => 'Required application permissions',
|
|
'status' => 'fail',
|
|
'severity' => 'critical',
|
|
'blocking' => true,
|
|
'reason_code' => 'ext.missing_permission',
|
|
'message' => 'Missing required application permissions.',
|
|
'evidence' => [
|
|
['kind' => 'missing_permission', 'value' => 'DeviceManagementConfiguration.Read.All'],
|
|
],
|
|
'next_steps' => [
|
|
[
|
|
'label' => 'Open required permissions',
|
|
'url' => RequiredPermissionsLinks::requiredPermissions($tenant),
|
|
],
|
|
],
|
|
],
|
|
[
|
|
'key' => 'permissions.intune_configuration',
|
|
'title' => 'Intune configuration access',
|
|
'status' => 'pass',
|
|
'severity' => 'info',
|
|
'blocking' => false,
|
|
'reason_code' => 'ok',
|
|
'message' => 'All required permissions are granted.',
|
|
'evidence' => [],
|
|
'next_steps' => [],
|
|
],
|
|
];
|
|
|
|
$verificationReport = VerificationReportWriter::build('provider.connection.check', $checks);
|
|
|
|
$run = OperationRun::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'type' => 'provider.connection.check',
|
|
'status' => 'completed',
|
|
'outcome' => 'succeeded',
|
|
'context' => [
|
|
'target_scope' => [
|
|
'entra_tenant_id' => $entraTenantId,
|
|
'entra_tenant_name' => 'Contoso',
|
|
],
|
|
'verification_report' => $verificationReport,
|
|
],
|
|
]);
|
|
|
|
TenantOnboardingSession::query()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'entra_tenant_id' => $entraTenantId,
|
|
'current_step' => 'verify',
|
|
'state' => [
|
|
'verification_operation_run_id' => (int) $run->getKey(),
|
|
],
|
|
'started_by_user_id' => (int) $user->getKey(),
|
|
'updated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->followingRedirects()
|
|
->get('/admin/onboarding')
|
|
->assertSuccessful()
|
|
->assertSee('Technical details')
|
|
->assertSee('Required application permissions')
|
|
->assertSee('Open required permissions')
|
|
->assertSee('Issues')
|
|
->assertSee($entraTenantId);
|
|
});
|
|
|
|
it('can open the onboarding verification technical details slideover without errors', function (): void {
|
|
$workspace = Workspace::factory()->create();
|
|
$user = User::factory()->create();
|
|
|
|
WorkspaceMembership::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'user_id' => (int) $user->getKey(),
|
|
'role' => 'owner',
|
|
]);
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
|
|
|
|
$entraTenantId = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
|
|
|
|
$tenant = Tenant::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => $entraTenantId,
|
|
'external_id' => 'tenant-clusters-b',
|
|
'status' => 'onboarding',
|
|
]);
|
|
|
|
$user->tenants()->syncWithoutDetaching([
|
|
$tenant->getKey() => ['role' => 'owner'],
|
|
]);
|
|
|
|
$verificationReport = VerificationReportWriter::build('provider.connection.check', []);
|
|
|
|
$run = OperationRun::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'type' => 'provider.connection.check',
|
|
'status' => 'completed',
|
|
'outcome' => 'succeeded',
|
|
'context' => [
|
|
'target_scope' => [
|
|
'entra_tenant_id' => $entraTenantId,
|
|
'entra_tenant_name' => 'Contoso',
|
|
],
|
|
'verification_report' => $verificationReport,
|
|
],
|
|
]);
|
|
|
|
$session = TenantOnboardingSession::query()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'entra_tenant_id' => $entraTenantId,
|
|
'current_step' => 'verify',
|
|
'state' => [
|
|
'verification_operation_run_id' => (int) $run->getKey(),
|
|
],
|
|
'started_by_user_id' => (int) $user->getKey(),
|
|
'updated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
|
|
Livewire::test(ManagedTenantOnboardingWizard::class, [
|
|
'onboardingDraft' => (int) $session->getKey(),
|
|
])
|
|
->mountAction('wizardVerificationTechnicalDetails')
|
|
->assertSuccessful();
|
|
});
|
|
|
|
it('routes permission-related verification next steps through the required permissions assist', function (): void {
|
|
$workspace = Workspace::factory()->create();
|
|
$user = User::factory()->create();
|
|
|
|
WorkspaceMembership::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'user_id' => (int) $user->getKey(),
|
|
'role' => 'owner',
|
|
]);
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
|
|
|
|
$tenant = Tenant::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => 'cccccccc-cccc-cccc-cccc-cccccccccccc',
|
|
'external_id' => 'tenant-clusters-c',
|
|
'status' => 'onboarding',
|
|
]);
|
|
|
|
$user->tenants()->syncWithoutDetaching([
|
|
$tenant->getKey() => ['role' => 'owner'],
|
|
]);
|
|
|
|
$connection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'provider' => 'microsoft',
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
]);
|
|
|
|
$verificationReport = VerificationReportWriter::build('provider.connection.check', [
|
|
[
|
|
'key' => 'provider.connection.preflight',
|
|
'title' => 'Required application permissions',
|
|
'status' => 'fail',
|
|
'severity' => 'critical',
|
|
'blocking' => true,
|
|
'reason_code' => 'provider_permission_missing',
|
|
'message' => 'Missing required application permissions.',
|
|
'evidence' => [],
|
|
'next_steps' => [
|
|
[
|
|
'label' => 'Grant admin consent',
|
|
'url' => 'https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/grant-admin-consent',
|
|
],
|
|
[
|
|
'label' => 'Review platform connection',
|
|
'url' => ProviderConnectionResource::getUrl(
|
|
'edit',
|
|
['tenant' => $tenant->external_id, 'record' => (int) $connection->getKey()],
|
|
panel: 'admin',
|
|
),
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
|
|
$run = OperationRun::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'type' => 'provider.connection.check',
|
|
'status' => 'completed',
|
|
'outcome' => 'blocked',
|
|
'context' => [
|
|
'target_scope' => [
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'entra_tenant_name' => (string) $tenant->name,
|
|
],
|
|
'verification_report' => $verificationReport,
|
|
],
|
|
]);
|
|
|
|
TenantOnboardingSession::query()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'current_step' => 'verify',
|
|
'state' => [
|
|
'verification_operation_run_id' => (int) $run->getKey(),
|
|
],
|
|
'started_by_user_id' => (int) $user->getKey(),
|
|
'updated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->followingRedirects()
|
|
->get('/admin/onboarding')
|
|
->assertSuccessful()
|
|
->assertSee('Grant admin consent')
|
|
->assertSee('Review platform connection')
|
|
->assertSee('Open in assist')
|
|
->assertSee('data-testid="verification-next-step-grant-admin-consent"', false)
|
|
->assertSee('data-testid="verification-next-step-review-platform-connection"', false)
|
|
->assertSee('wire:click="mountAction(\'wizardVerificationRequiredPermissionsAssist\')"', false)
|
|
->assertDontSee('href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/grant-admin-consent"', false)
|
|
->assertDontSee(ProviderConnectionResource::getUrl(
|
|
'edit',
|
|
['tenant' => $tenant->external_id, 'record' => (int) $connection->getKey()],
|
|
panel: 'admin',
|
|
), false);
|
|
});
|