114 lines
4.6 KiB
PHP
114 lines
4.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\{ConnectedApp, ConnectionProtocol, ConnectionProvider, ConnectionStatus, User};
|
|
use Illuminate\Support\Facades\File;
|
|
|
|
beforeEach(function (): void {
|
|
// Generate testing keys if not already present
|
|
$directory = storage_path('app/saml');
|
|
if (! File::exists($directory)) {
|
|
File::makeDirectory($directory, 0755, true);
|
|
}
|
|
|
|
if (! File::exists("{$directory}/idp.key") || ! File::exists("{$directory}/idp.crt")) {
|
|
$this->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 = '<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_mock_request_id" Version="2.0" IssueInstant="2026-05-26T12:00:00Z" AssertionConsumerServiceURL="https://login.microsoftonline.com/login.srf" Destination="http://localhost/saml/sso"><saml:Issuer>urn:federation:MicrosoftOnline</saml:Issuer></samlp:AuthnRequest>';
|
|
$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 = '<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_mock_request_id" Version="2.0" IssueInstant="2026-05-26T12:00:00Z" AssertionConsumerServiceURL="https://login.microsoftonline.com/login.srf" Destination="http://localhost/saml/sso"><saml:Issuer>urn:federation:MicrosoftOnline</saml:Issuer></samlp:AuthnRequest>';
|
|
$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();
|
|
});
|