215 lines
8.1 KiB
PHP
215 lines
8.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Workspaces\ManagedTenantOnboardingWizard;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\Tenant;
|
|
use App\Models\TenantOnboardingSession;
|
|
use App\Models\TenantPermission;
|
|
use App\Models\User;
|
|
use App\Models\Workspace;
|
|
use App\Models\WorkspaceMembership;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\Providers\ProviderReasonCodes;
|
|
use App\Support\Verification\VerificationReportWriter;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Livewire\Livewire;
|
|
|
|
describe('Onboarding wizard UI enforcement', function () {
|
|
it('denies identifyManagedTenant for readonly workspace members', function (): void {
|
|
$workspace = Workspace::factory()->create();
|
|
$user = User::factory()->create();
|
|
|
|
WorkspaceMembership::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'user_id' => (int) $user->getKey(),
|
|
'role' => 'readonly',
|
|
]);
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ManagedTenantOnboardingWizard::class)
|
|
->call('identifyManagedTenant', [
|
|
'entra_tenant_id' => '11111111-1111-1111-1111-111111111111',
|
|
'environment' => 'prod',
|
|
'name' => 'Acme',
|
|
])
|
|
->assertStatus(403);
|
|
});
|
|
|
|
it('denies provider connection creation for operator members', function (): void {
|
|
$workspace = Workspace::factory()->create();
|
|
$user = User::factory()->create();
|
|
|
|
WorkspaceMembership::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'user_id' => (int) $user->getKey(),
|
|
'role' => 'operator',
|
|
]);
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ManagedTenantOnboardingWizard::class)
|
|
->call('createProviderConnection', [
|
|
'display_name' => 'Acme connection',
|
|
'client_id' => '00000000-0000-0000-0000-000000000000',
|
|
'client_secret' => 'super-secret',
|
|
'is_default' => true,
|
|
])
|
|
->assertStatus(403);
|
|
});
|
|
|
|
it('allows operator members to start verification for an existing onboarding session', function (): void {
|
|
Queue::fake();
|
|
|
|
$workspace = Workspace::factory()->create();
|
|
$user = User::factory()->create();
|
|
|
|
WorkspaceMembership::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'user_id' => (int) $user->getKey(),
|
|
'role' => 'operator',
|
|
]);
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
|
|
|
|
$tenant = Tenant::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'status' => Tenant::STATUS_ONBOARDING,
|
|
]);
|
|
|
|
$connection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
]);
|
|
|
|
TenantOnboardingSession::query()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'current_step' => 'connection',
|
|
'state' => [
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
],
|
|
'started_by_user_id' => (int) $user->getKey(),
|
|
'updated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ManagedTenantOnboardingWizard::class)
|
|
->call('startVerification');
|
|
|
|
$run = OperationRun::query()
|
|
->where('tenant_id', (int) $tenant->getKey())
|
|
->where('type', 'provider.connection.check')
|
|
->latest('id')
|
|
->first();
|
|
|
|
expect($run)->not->toBeNull();
|
|
|
|
Queue::assertNothingPushed();
|
|
});
|
|
|
|
it('returns 403 for in-scope members missing onboarding capability on assist-backed drafts', function (): void {
|
|
$configured = array_merge(
|
|
config('intune_permissions.permissions', []),
|
|
config('entra_permissions.permissions', []),
|
|
);
|
|
|
|
$permissionKeys = array_values(array_filter(array_map(static function (mixed $permission): ?string {
|
|
if (! is_array($permission)) {
|
|
return null;
|
|
}
|
|
|
|
$key = $permission['key'] ?? null;
|
|
|
|
return is_string($key) && trim($key) !== '' ? trim($key) : null;
|
|
}, $configured)));
|
|
|
|
if ($permissionKeys === []) {
|
|
test()->markTestSkipped('No configured required permissions found.');
|
|
}
|
|
|
|
[$user, $tenant] = createUserWithTenant(
|
|
role: 'operator',
|
|
workspaceRole: 'operator',
|
|
ensureDefaultMicrosoftProviderConnection: false,
|
|
);
|
|
|
|
$workspace = $tenant->workspace()->firstOrFail();
|
|
|
|
foreach ($permissionKeys as $key) {
|
|
TenantPermission::query()->create([
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'permission_key' => $key,
|
|
'status' => 'granted',
|
|
'details' => ['source' => 'db'],
|
|
'last_checked_at' => now()->subDays(45),
|
|
]);
|
|
}
|
|
|
|
$connection = ProviderConnection::factory()->platform()->consentGranted()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'provider' => 'microsoft',
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'is_default' => true,
|
|
'status' => 'connected',
|
|
]);
|
|
|
|
$run = OperationRun::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'type' => 'provider.connection.check',
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Succeeded->value,
|
|
'context' => [
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'target_scope' => [
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'entra_tenant_name' => (string) $tenant->name,
|
|
],
|
|
'verification_report' => VerificationReportWriter::build('provider.connection.check', [
|
|
[
|
|
'key' => 'permissions.admin_consent',
|
|
'title' => 'Required application permissions',
|
|
'status' => 'warn',
|
|
'severity' => 'medium',
|
|
'blocking' => false,
|
|
'reason_code' => ProviderReasonCodes::ProviderPermissionRefreshFailed,
|
|
'message' => 'Stored permission data needs review.',
|
|
'evidence' => [],
|
|
'next_steps' => [],
|
|
],
|
|
]),
|
|
],
|
|
]);
|
|
|
|
$draft = TenantOnboardingSession::query()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'current_step' => 'verify',
|
|
'state' => [
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'verification_operation_run_id' => (int) $run->getKey(),
|
|
],
|
|
'started_by_user_id' => (int) $user->getKey(),
|
|
'updated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
|
|
->get(route('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()]))
|
|
->assertForbidden();
|
|
});
|
|
});
|