artisan('saml:generate-keys'); } $this->seed(Database\Seeders\DatabaseSeeder::class); // Get seeded protocol, provider, and status $this->protocol = ConnectionProtocol::query()->where('name', 'saml')->firstOrFail(); $this->provider = ConnectionProvider::query()->where('slug', 'azure-fd')->firstOrFail(); $this->status = ConnectionStatus::query()->where('name', 'connected')->firstOrFail(); // Create a mock SAML App $this->samlApp = ConnectedApp::query()->create([ 'name' => 'Microsoft Office 365', 'slug' => 'microsoft-office-365', 'connection_protocol_id' => $this->protocol->id, 'connection_provider_id' => $this->provider->id, 'connection_status_id' => $this->status->id, 'settings' => [ 'saml' => [ 'entity_id' => 'urn:federation:MicrosoftOnline', 'acs_url' => 'https://login.microsoftonline.com/login.srf', ], ], ]); // Create a mock User $this->user = User::factory()->create([ 'email' => 'testuser@company.com', 'immutable_id' => 'user-123-immutable', ]); }); test('saml metadata endpoint returns active public certificate and sso location', function (): void { $response = $this->get(route('saml.metadata')); $response->assertStatus(200); $response->assertHeader('Content-Type', 'application/xml; charset=utf-8'); $content = $response->getContent(); expect($content)->toContain('EntityDescriptor') ->toContain('IDPSSODescriptor') ->toContain('SingleSignOnService') ->toContain(route('saml.sso')) ->toContain('X509Certificate'); }); test('saml sso endpoint aborts if missing SAMLRequest', function (): void { $response = $this->get(route('saml.sso')); $response->assertStatus(400); }); test('saml sso endpoint redirects unauthenticated users to login page and remembers request', function (): void { $xml = 'urn:federation:MicrosoftOnline'; $samlRequest = base64_encode(gzdeflate($xml)); $relayState = 'https://teams.microsoft.com/'; $response = $this->get(route('saml.sso', [ 'SAMLRequest' => $samlRequest, 'RelayState' => $relayState, ])); $response->assertRedirect(route('login')); // Assert session retains pending details expect(session('saml_pending_request'))->toBe($samlRequest); expect(session('saml_pending_relay_state'))->toBe($relayState); }); test('saml sso endpoint signs and redirects authenticated users via POST binding', function (): void { $xml = 'urn:federation:MicrosoftOnline'; $samlRequest = base64_encode(gzdeflate($xml)); $relayState = 'https://teams.microsoft.com/'; // Log in the user $this->actingAs($this->user); $response = $this->get(route('saml.sso', [ 'SAMLRequest' => $samlRequest, 'RelayState' => $relayState, ])); $response->assertStatus(200); $response->assertViewIs('saml.post_response'); $response->assertViewHasAll([ 'appName' => 'Microsoft Office 365', 'acsUrl' => 'https://login.microsoftonline.com/login.srf', 'relayState' => $relayState, ]); $content = $response->getContent(); expect($content)->toContain('name="SAMLResponse"') ->toContain('name="RelayState"') ->toContain('value="'.$relayState.'"'); // Verify session was cleared expect(session('saml_pending_request'))->toBeNull(); });