- added a ImmuatableId field in manage roles - Generate ImmutableId using user Id if not available
160 lines
6.5 KiB
PHP
160 lines
6.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\{ConnectedApp, ConnectionProtocol, ConnectionProvider, ConnectionStatus, User};
|
|
use Illuminate\Support\Facades\File;
|
|
use Livewire\Livewire;
|
|
|
|
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();
|
|
});
|
|
|
|
test('user sso settings and immutable id can be assigned via service and mapped to DTO', function (): void {
|
|
$userService = app(App\Services\UserService::class);
|
|
|
|
// Assign roles and update immutable ID
|
|
$userService->assignRoles($this->user->id, [], 'new-custom-sso-immutable-id');
|
|
|
|
// Retrieve fresh user and assert
|
|
$freshUser = $this->user->fresh();
|
|
expect($freshUser->immutable_id)->toBe('new-custom-sso-immutable-id');
|
|
|
|
// Assert UserIndexData maps it correctly
|
|
$dto = App\Data\User\UserIndexData::fromModel($freshUser);
|
|
expect($dto->immutableId)->toBe('new-custom-sso-immutable-id');
|
|
});
|
|
|
|
test('saml settings field is only visible if selected roles contain a SAML app', function (): void {
|
|
// Create a role with a SAML connected app
|
|
$samlRole = App\Models\Role::findOrCreate('SAML User');
|
|
$samlRole->update(['priority' => 10]);
|
|
$samlRole->apps()->attach($this->samlApp->id, ['duration' => 365]);
|
|
|
|
// Create a role with no connected apps
|
|
$emptyRole = App\Models\Role::findOrCreate('Empty User');
|
|
$emptyRole->update(['priority' => 20]);
|
|
|
|
// Log in the administrator so they can view users
|
|
$admin = User::where('email', 'admin@example.com')->firstOrFail();
|
|
$this->actingAs($admin);
|
|
|
|
// 1. Livewire component with no roles selected should return false for showSamlSettings
|
|
Livewire::test('pages::access-manager.users.index')
|
|
->set('form.roleIds', [])
|
|
->assertSet('showSamlSettings', false);
|
|
|
|
// 2. Livewire component with emptyRole selected should return false
|
|
Livewire::test('pages::access-manager.users.index')
|
|
->set('form.roleIds', [$emptyRole->id])
|
|
->assertSet('showSamlSettings', false);
|
|
|
|
// 3. Livewire component with samlRole selected should return true
|
|
Livewire::test('pages::access-manager.users.index')
|
|
->set('form.roleIds', [$samlRole->id])
|
|
->assertSet('showSamlSettings', true);
|
|
});
|