2021-06-26 17:23:15 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Tests\Auth;
|
2019-11-17 14:26:43 +01:00
|
|
|
|
2023-05-17 18:56:55 +02:00
|
|
|
use BookStack\Users\Models\Role;
|
|
|
|
use BookStack\Users\Models\User;
|
2020-04-04 02:16:05 +02:00
|
|
|
use Tests\TestCase;
|
2019-11-17 14:26:43 +01:00
|
|
|
|
2019-12-22 14:17:14 +01:00
|
|
|
class Saml2Test extends TestCase
|
2019-11-17 14:26:43 +01:00
|
|
|
{
|
2021-10-30 22:29:59 +02:00
|
|
|
protected function setUp(): void
|
2019-11-17 14:26:43 +01:00
|
|
|
{
|
|
|
|
parent::setUp();
|
|
|
|
// Set default config for SAML2
|
|
|
|
config()->set([
|
2021-06-26 17:23:15 +02:00
|
|
|
'auth.method' => 'saml2',
|
|
|
|
'auth.defaults.guard' => 'saml2',
|
|
|
|
'saml2.name' => 'SingleSignOn-Testing',
|
|
|
|
'saml2.email_attribute' => 'email',
|
|
|
|
'saml2.display_name_attributes' => ['first_name', 'last_name'],
|
|
|
|
'saml2.external_id_attribute' => 'uid',
|
|
|
|
'saml2.user_to_groups' => false,
|
|
|
|
'saml2.group_attribute' => 'user_groups',
|
|
|
|
'saml2.remove_from_groups' => false,
|
|
|
|
'saml2.onelogin_overrides' => null,
|
|
|
|
'saml2.onelogin.idp.entityId' => 'http://saml.local/saml2/idp/metadata.php',
|
|
|
|
'saml2.onelogin.idp.singleSignOnService.url' => 'http://saml.local/saml2/idp/SSOService.php',
|
|
|
|
'saml2.onelogin.idp.singleLogoutService.url' => 'http://saml.local/saml2/idp/SingleLogoutService.php',
|
|
|
|
'saml2.autoload_from_metadata' => false,
|
|
|
|
'saml2.onelogin.idp.x509cert' => $this->testCert,
|
|
|
|
'saml2.onelogin.debug' => false,
|
2021-05-08 14:07:25 +02:00
|
|
|
'saml2.onelogin.security.requestedAuthnContext' => true,
|
2019-11-17 14:26:43 +01:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_metadata_endpoint_displays_xml_as_expected()
|
|
|
|
{
|
|
|
|
$req = $this->get('/saml2/metadata');
|
|
|
|
$req->assertHeader('Content-Type', 'text/xml; charset=UTF-8');
|
|
|
|
$req->assertSee('md:EntityDescriptor');
|
|
|
|
$req->assertSee(url('/saml2/acs'));
|
|
|
|
}
|
|
|
|
|
2022-10-16 10:50:08 +02:00
|
|
|
public function test_metadata_endpoint_loads_when_autoloading_with_bad_url_set()
|
|
|
|
{
|
|
|
|
config()->set([
|
|
|
|
'saml2.autoload_from_metadata' => true,
|
|
|
|
'saml2.onelogin.idp.entityId' => 'http://192.168.1.1:9292',
|
|
|
|
'saml2.onelogin.idp.singleSignOnService.url' => null,
|
|
|
|
]);
|
|
|
|
|
|
|
|
$req = $this->get('/saml2/metadata');
|
|
|
|
$req->assertOk();
|
|
|
|
$req->assertHeader('Content-Type', 'text/xml; charset=UTF-8');
|
|
|
|
$req->assertSee('md:EntityDescriptor');
|
|
|
|
}
|
|
|
|
|
2019-11-17 14:26:43 +01:00
|
|
|
public function test_onelogin_overrides_functions_as_expected()
|
|
|
|
{
|
|
|
|
$json = '{"sp": {"assertionConsumerService": {"url": "https://example.com/super-cats"}}, "contactPerson": {"technical": {"givenName": "Barry Scott", "emailAddress": "barry@example.com"}}}';
|
|
|
|
config()->set(['saml2.onelogin_overrides' => $json]);
|
|
|
|
|
|
|
|
$req = $this->get('/saml2/metadata');
|
|
|
|
$req->assertSee('https://example.com/super-cats');
|
|
|
|
$req->assertSee('md:ContactPerson');
|
2021-10-26 23:04:18 +02:00
|
|
|
$req->assertSee('<md:GivenName>Barry Scott</md:GivenName>', false);
|
2019-11-17 14:26:43 +01:00
|
|
|
}
|
2019-11-17 20:15:37 +01:00
|
|
|
|
|
|
|
public function test_login_option_shows_on_login_page()
|
|
|
|
{
|
|
|
|
$req = $this->get('/login');
|
|
|
|
$req->assertSeeText('SingleSignOn-Testing');
|
2022-07-23 16:10:18 +02:00
|
|
|
$this->withHtml($req)->assertElementExists('form[action$="/saml2/login"][method=POST] button');
|
2019-11-17 20:15:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function test_login()
|
|
|
|
{
|
2020-02-02 13:00:41 +01:00
|
|
|
$req = $this->post('/saml2/login');
|
2019-11-17 20:15:37 +01:00
|
|
|
$redirect = $req->headers->get('location');
|
|
|
|
$this->assertStringStartsWith('http://saml.local/saml2/idp/SSOService.php', $redirect, 'Login redirects to SSO location');
|
|
|
|
|
|
|
|
config()->set(['saml2.onelogin.strict' => false]);
|
|
|
|
$this->assertFalse($this->isAuthenticated());
|
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$acsPost = $this->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
$redirect = $acsPost->headers->get('Location');
|
|
|
|
$acsId = explode('?id=', $redirect)[1];
|
|
|
|
$this->assertTrue(strlen($acsId) > 12);
|
|
|
|
|
|
|
|
$this->assertStringContainsString('/saml2/acs?id=', $redirect);
|
|
|
|
$this->assertTrue(cache()->has('saml2_acs:' . $acsId));
|
|
|
|
|
|
|
|
$acsGet = $this->get($redirect);
|
|
|
|
$acsGet->assertRedirect('/');
|
|
|
|
$this->assertFalse(cache()->has('saml2_acs:' . $acsId));
|
|
|
|
|
|
|
|
$this->assertTrue($this->isAuthenticated());
|
|
|
|
$this->assertDatabaseHas('users', [
|
|
|
|
'email' => 'user@example.com',
|
|
|
|
'external_auth_id' => 'user',
|
|
|
|
'email_confirmed' => false,
|
|
|
|
'name' => 'Barry Scott',
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_acs_process_id_randomly_generated()
|
|
|
|
{
|
|
|
|
$acsPost = $this->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
$redirectA = $acsPost->headers->get('Location');
|
|
|
|
|
|
|
|
$acsPost = $this->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
$redirectB = $acsPost->headers->get('Location');
|
|
|
|
|
|
|
|
$this->assertFalse($redirectA === $redirectB);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_process_acs_endpoint_cant_be_called_with_invalid_id()
|
|
|
|
{
|
|
|
|
$resp = $this->get('/saml2/acs');
|
|
|
|
$resp->assertRedirect('/login');
|
|
|
|
$this->followRedirects($resp)->assertSeeText('Login using SingleSignOn-Testing failed, system did not provide successful authorization');
|
|
|
|
|
|
|
|
$resp = $this->get('/saml2/acs?id=abc123');
|
|
|
|
$resp->assertRedirect('/login');
|
|
|
|
$this->followRedirects($resp)->assertSeeText('Login using SingleSignOn-Testing failed, system did not provide successful authorization');
|
2019-11-17 20:15:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function test_group_role_sync_on_login()
|
|
|
|
{
|
|
|
|
config()->set([
|
2021-06-26 17:23:15 +02:00
|
|
|
'saml2.onelogin.strict' => false,
|
|
|
|
'saml2.user_to_groups' => true,
|
2019-11-17 20:15:37 +01:00
|
|
|
'saml2.remove_from_groups' => false,
|
|
|
|
]);
|
|
|
|
|
2021-10-30 22:29:59 +02:00
|
|
|
$memberRole = Role::factory()->create(['external_auth_id' => 'member']);
|
2019-11-17 20:15:37 +01:00
|
|
|
$adminRole = Role::getSystemRole('admin');
|
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
$user = User::query()->where('external_auth_id', '=', 'user')->first();
|
2019-11-17 20:15:37 +01:00
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$userRoleIds = $user->roles()->pluck('id');
|
|
|
|
$this->assertContains($memberRole->id, $userRoleIds, 'User was assigned to member role');
|
|
|
|
$this->assertContains($adminRole->id, $userRoleIds, 'User was assigned to admin role');
|
2019-11-17 20:15:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function test_group_role_sync_removal_option_works_as_expected()
|
|
|
|
{
|
|
|
|
config()->set([
|
2021-06-26 17:23:15 +02:00
|
|
|
'saml2.onelogin.strict' => false,
|
|
|
|
'saml2.user_to_groups' => true,
|
2019-11-17 20:15:37 +01:00
|
|
|
'saml2.remove_from_groups' => true,
|
|
|
|
]);
|
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$acsPost = $this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
$user = User::query()->where('external_auth_id', '=', 'user')->first();
|
2019-11-17 20:15:37 +01:00
|
|
|
|
2021-10-30 22:29:59 +02:00
|
|
|
$randomRole = Role::factory()->create(['external_auth_id' => 'random']);
|
2021-10-20 14:30:45 +02:00
|
|
|
$user->attachRole($randomRole);
|
|
|
|
$this->assertContains($randomRole->id, $user->roles()->pluck('id'));
|
2019-11-17 20:15:37 +01:00
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
auth()->logout();
|
|
|
|
$acsPost = $this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
$this->assertNotContains($randomRole->id, $user->roles()->pluck('id'));
|
2019-11-17 20:15:37 +01:00
|
|
|
}
|
|
|
|
|
2020-02-02 13:00:41 +01:00
|
|
|
public function test_logout_link_directs_to_saml_path()
|
2019-11-17 20:15:37 +01:00
|
|
|
{
|
|
|
|
config()->set([
|
|
|
|
'saml2.onelogin.strict' => false,
|
|
|
|
]);
|
|
|
|
|
2023-01-21 12:08:34 +01:00
|
|
|
$resp = $this->actingAs($this->users->editor())->get('/');
|
2022-07-23 16:10:18 +02:00
|
|
|
$this->withHtml($resp)->assertElementContains('form[action$="/saml2/logout"] button', 'Logout');
|
2019-11-17 20:15:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function test_logout_sls_flow()
|
|
|
|
{
|
|
|
|
config()->set([
|
|
|
|
'saml2.onelogin.strict' => false,
|
|
|
|
]);
|
|
|
|
|
|
|
|
$handleLogoutResponse = function () {
|
2023-12-08 19:38:52 +01:00
|
|
|
$this->assertFalse($this->isAuthenticated());
|
2019-11-17 20:15:37 +01:00
|
|
|
|
|
|
|
$req = $this->get('/saml2/sls');
|
|
|
|
$req->assertRedirect('/');
|
|
|
|
$this->assertFalse($this->isAuthenticated());
|
|
|
|
};
|
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
2019-11-17 20:15:37 +01:00
|
|
|
|
2021-11-14 22:13:24 +01:00
|
|
|
$req = $this->post('/saml2/logout');
|
2021-10-20 14:30:45 +02:00
|
|
|
$redirect = $req->headers->get('location');
|
|
|
|
$this->assertStringStartsWith('http://saml.local/saml2/idp/SingleLogoutService.php', $redirect);
|
2023-04-28 14:54:51 +02:00
|
|
|
$sloData = $this->parseSamlDataFromUrl($redirect, 'SAMLRequest');
|
|
|
|
$this->assertStringContainsString('<samlp:SessionIndex>_4fe7c0d1572d64b27f930aa6f236a6f42e930901cc</samlp:SessionIndex>', $sloData);
|
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$this->withGet(['SAMLResponse' => $this->sloResponseData], $handleLogoutResponse);
|
2019-11-17 20:15:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function test_logout_sls_flow_when_sls_not_configured()
|
|
|
|
{
|
|
|
|
config()->set([
|
2021-06-26 17:23:15 +02:00
|
|
|
'saml2.onelogin.strict' => false,
|
2019-11-17 20:15:37 +01:00
|
|
|
'saml2.onelogin.idp.singleLogoutService.url' => null,
|
|
|
|
]);
|
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
$this->assertTrue($this->isAuthenticated());
|
2019-11-17 20:15:37 +01:00
|
|
|
|
2021-11-14 22:13:24 +01:00
|
|
|
$req = $this->post('/saml2/logout');
|
2021-10-20 14:30:45 +02:00
|
|
|
$req->assertRedirect('/');
|
|
|
|
$this->assertFalse($this->isAuthenticated());
|
2019-11-17 20:15:37 +01:00
|
|
|
}
|
|
|
|
|
2023-12-08 19:38:52 +01:00
|
|
|
public function test_logout_sls_flow_logs_user_out_before_redirect()
|
|
|
|
{
|
|
|
|
config()->set([
|
|
|
|
'saml2.onelogin.strict' => false,
|
|
|
|
]);
|
|
|
|
|
|
|
|
$this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
$this->assertTrue($this->isAuthenticated());
|
|
|
|
|
|
|
|
$req = $this->post('/saml2/logout');
|
|
|
|
$redirect = $req->headers->get('location');
|
|
|
|
$this->assertStringStartsWith('http://saml.local/saml2/idp/SingleLogoutService.php', $redirect);
|
|
|
|
$this->assertFalse($this->isAuthenticated());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_logout_sls_request_redirect_prevents_auto_login_when_enabled()
|
|
|
|
{
|
|
|
|
config()->set([
|
|
|
|
'saml2.onelogin.strict' => false,
|
|
|
|
'auth.auto_initiate' => true,
|
|
|
|
'services.google.client_id' => false,
|
|
|
|
'services.github.client_id' => false,
|
|
|
|
]);
|
|
|
|
|
|
|
|
$this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
|
|
|
|
$req = $this->post('/saml2/logout');
|
|
|
|
$redirect = $req->headers->get('location');
|
|
|
|
$this->assertStringContainsString(urlencode(url('/login?prevent_auto_init=true')), $redirect);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_logout_sls_response_endpoint_redirect_prevents_auto_login_when_enabled()
|
|
|
|
{
|
|
|
|
config()->set([
|
|
|
|
'saml2.onelogin.strict' => false,
|
|
|
|
'auth.auto_initiate' => true,
|
|
|
|
'services.google.client_id' => false,
|
|
|
|
'services.github.client_id' => false,
|
|
|
|
]);
|
|
|
|
|
|
|
|
$this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
|
|
|
|
$this->withGet(['SAMLResponse' => $this->sloResponseData], function () {
|
|
|
|
$req = $this->get('/saml2/sls');
|
|
|
|
$redirect = $req->headers->get('location');
|
|
|
|
$this->assertEquals(url('/login?prevent_auto_init=true'), $redirect);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-11-17 20:15:37 +01:00
|
|
|
public function test_dump_user_details_option_works()
|
|
|
|
{
|
|
|
|
config()->set([
|
2021-06-26 17:23:15 +02:00
|
|
|
'saml2.onelogin.strict' => false,
|
2019-11-17 20:15:37 +01:00
|
|
|
'saml2.dump_user_details' => true,
|
|
|
|
]);
|
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$acsPost = $this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
$acsPost->assertJsonStructure([
|
|
|
|
'id_from_idp',
|
|
|
|
'attrs_from_idp' => [],
|
|
|
|
'attrs_after_parsing' => [],
|
|
|
|
]);
|
2019-11-17 20:15:37 +01:00
|
|
|
}
|
|
|
|
|
2023-12-03 20:35:05 +01:00
|
|
|
public function test_dump_user_details_response_contains_parsed_group_data_if_groups_enabled()
|
|
|
|
{
|
|
|
|
config()->set([
|
|
|
|
'saml2.onelogin.strict' => false,
|
|
|
|
'saml2.dump_user_details' => true,
|
|
|
|
'saml2.user_to_groups' => true,
|
|
|
|
]);
|
|
|
|
|
|
|
|
$acsPost = $this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
$acsPost->assertJson([
|
|
|
|
'attrs_after_parsing' => [
|
|
|
|
'groups' => ['member', 'admin'],
|
|
|
|
]
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2019-11-17 20:15:37 +01:00
|
|
|
public function test_saml_routes_are_only_active_if_saml_enabled()
|
|
|
|
{
|
2020-02-02 13:00:41 +01:00
|
|
|
config()->set(['auth.method' => 'standard']);
|
2021-11-14 22:13:24 +01:00
|
|
|
$getRoutes = ['/metadata', '/sls'];
|
2019-11-17 20:15:37 +01:00
|
|
|
foreach ($getRoutes as $route) {
|
|
|
|
$req = $this->get('/saml2' . $route);
|
2020-02-02 14:10:21 +01:00
|
|
|
$this->assertPermissionError($req);
|
2019-11-17 20:15:37 +01:00
|
|
|
}
|
|
|
|
|
2021-11-14 22:13:24 +01:00
|
|
|
$postRoutes = ['/login', '/acs', '/logout'];
|
2019-11-17 20:15:37 +01:00
|
|
|
foreach ($postRoutes as $route) {
|
|
|
|
$req = $this->post('/saml2' . $route);
|
2020-02-02 14:10:21 +01:00
|
|
|
$this->assertPermissionError($req);
|
2019-11-17 20:15:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-02 14:10:21 +01:00
|
|
|
public function test_forgot_password_routes_inaccessible()
|
|
|
|
{
|
|
|
|
$resp = $this->get('/password/email');
|
|
|
|
$this->assertPermissionError($resp);
|
|
|
|
|
|
|
|
$resp = $this->post('/password/email');
|
|
|
|
$this->assertPermissionError($resp);
|
|
|
|
|
|
|
|
$resp = $this->get('/password/reset/abc123');
|
|
|
|
$this->assertPermissionError($resp);
|
|
|
|
|
|
|
|
$resp = $this->post('/password/reset');
|
|
|
|
$this->assertPermissionError($resp);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_standard_login_routes_inaccessible()
|
|
|
|
{
|
|
|
|
$resp = $this->post('/login');
|
|
|
|
$this->assertPermissionError($resp);
|
|
|
|
|
2021-11-14 22:13:24 +01:00
|
|
|
$resp = $this->post('/logout');
|
2020-02-02 14:10:21 +01:00
|
|
|
$this->assertPermissionError($resp);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_user_invite_routes_inaccessible()
|
|
|
|
{
|
|
|
|
$resp = $this->get('/register/invite/abc123');
|
|
|
|
$this->assertPermissionError($resp);
|
|
|
|
|
|
|
|
$resp = $this->post('/register/invite/abc123');
|
|
|
|
$this->assertPermissionError($resp);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_user_register_routes_inaccessible()
|
|
|
|
{
|
|
|
|
$resp = $this->get('/register');
|
|
|
|
$this->assertPermissionError($resp);
|
|
|
|
|
|
|
|
$resp = $this->post('/register');
|
|
|
|
$this->assertPermissionError($resp);
|
|
|
|
}
|
|
|
|
|
2020-02-02 18:31:00 +01:00
|
|
|
public function test_email_domain_restriction_active_on_new_saml_login()
|
|
|
|
{
|
|
|
|
$this->setSettings([
|
2021-06-26 17:23:15 +02:00
|
|
|
'registration-restrict' => 'testing.com',
|
2020-02-02 18:31:00 +01:00
|
|
|
]);
|
|
|
|
config()->set([
|
|
|
|
'saml2.onelogin.strict' => false,
|
|
|
|
]);
|
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$acsPost = $this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
$acsPost->assertSeeText('That email domain does not have access to this application');
|
|
|
|
$this->assertFalse(auth()->check());
|
|
|
|
$this->assertDatabaseMissing('users', ['email' => 'user@example.com']);
|
2020-02-02 18:31:00 +01:00
|
|
|
}
|
|
|
|
|
2020-08-04 18:54:50 +02:00
|
|
|
public function test_group_sync_functions_when_email_confirmation_required()
|
|
|
|
{
|
|
|
|
setting()->put('registration-confirmation', 'true');
|
|
|
|
config()->set([
|
2021-06-26 17:23:15 +02:00
|
|
|
'saml2.onelogin.strict' => false,
|
|
|
|
'saml2.user_to_groups' => true,
|
2020-08-04 18:54:50 +02:00
|
|
|
'saml2.remove_from_groups' => false,
|
|
|
|
]);
|
|
|
|
|
2021-10-30 22:29:59 +02:00
|
|
|
$memberRole = Role::factory()->create(['external_auth_id' => 'member']);
|
2020-08-04 18:54:50 +02:00
|
|
|
$adminRole = Role::getSystemRole('admin');
|
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$acsPost = $this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
2020-09-05 18:26:48 +02:00
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$this->assertEquals('http://localhost/register/confirm', url()->current());
|
|
|
|
$acsPost->assertSee('Please check your email and click the confirmation button to access BookStack.');
|
|
|
|
/** @var User $user */
|
|
|
|
$user = User::query()->where('external_auth_id', '=', 'user')->first();
|
2020-08-04 18:54:50 +02:00
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$userRoleIds = $user->roles()->pluck('id');
|
|
|
|
$this->assertContains($memberRole->id, $userRoleIds, 'User was assigned to member role');
|
|
|
|
$this->assertContains($adminRole->id, $userRoleIds, 'User was assigned to admin role');
|
|
|
|
$this->assertFalse(boolval($user->email_confirmed), 'User email remains unconfirmed');
|
2020-08-04 18:54:50 +02:00
|
|
|
|
2021-08-07 22:18:59 +02:00
|
|
|
$this->assertNull(auth()->user());
|
2020-08-04 18:54:50 +02:00
|
|
|
$homeGet = $this->get('/');
|
2021-08-07 22:18:59 +02:00
|
|
|
$homeGet->assertRedirect('/login');
|
2020-08-04 18:54:50 +02:00
|
|
|
}
|
|
|
|
|
2020-09-26 17:43:06 +02:00
|
|
|
public function test_login_where_existing_non_saml_user_shows_warning()
|
|
|
|
{
|
|
|
|
$this->post('/saml2/login');
|
|
|
|
config()->set(['saml2.onelogin.strict' => false]);
|
|
|
|
|
|
|
|
// Make the user pre-existing in DB with different auth_id
|
|
|
|
User::query()->forceCreate([
|
2021-06-26 17:23:15 +02:00
|
|
|
'email' => 'user@example.com',
|
2020-09-26 17:43:06 +02:00
|
|
|
'external_auth_id' => 'old_system_user_id',
|
2021-06-26 17:23:15 +02:00
|
|
|
'email_confirmed' => false,
|
|
|
|
'name' => 'Barry Scott',
|
2020-09-26 17:43:06 +02:00
|
|
|
]);
|
|
|
|
|
2021-10-20 14:30:45 +02:00
|
|
|
$acsPost = $this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
|
|
|
|
$this->assertFalse($this->isAuthenticated());
|
|
|
|
$this->assertDatabaseHas('users', [
|
|
|
|
'email' => 'user@example.com',
|
|
|
|
'external_auth_id' => 'old_system_user_id',
|
|
|
|
]);
|
|
|
|
|
|
|
|
$acsPost->assertSee('A user with the email user@example.com already exists but with different credentials');
|
2020-09-26 17:43:06 +02:00
|
|
|
}
|
|
|
|
|
2021-05-08 14:07:25 +02:00
|
|
|
public function test_login_request_contains_expected_default_authncontext()
|
|
|
|
{
|
|
|
|
$authReq = $this->getAuthnRequest();
|
|
|
|
$this->assertStringContainsString('samlp:RequestedAuthnContext Comparison="exact"', $authReq);
|
|
|
|
$this->assertStringContainsString('<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>', $authReq);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_false_idp_authncontext_option_does_not_pass_authncontext_in_saml_request()
|
|
|
|
{
|
|
|
|
config()->set(['saml2.onelogin.security.requestedAuthnContext' => false]);
|
|
|
|
$authReq = $this->getAuthnRequest();
|
|
|
|
$this->assertStringNotContainsString('samlp:RequestedAuthnContext', $authReq);
|
|
|
|
$this->assertStringNotContainsString('<saml:AuthnContextClassRef>', $authReq);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_array_idp_authncontext_option_passes_value_as_authncontextclassref_in_request()
|
|
|
|
{
|
|
|
|
config()->set(['saml2.onelogin.security.requestedAuthnContext' => ['urn:federation:authentication:windows', 'urn:federation:authentication:linux']]);
|
|
|
|
$authReq = $this->getAuthnRequest();
|
|
|
|
$this->assertStringContainsString('samlp:RequestedAuthnContext', $authReq);
|
|
|
|
$this->assertStringContainsString('<saml:AuthnContextClassRef>urn:federation:authentication:windows</saml:AuthnContextClassRef>', $authReq);
|
|
|
|
$this->assertStringContainsString('<saml:AuthnContextClassRef>urn:federation:authentication:linux</saml:AuthnContextClassRef>', $authReq);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getAuthnRequest(): string
|
|
|
|
{
|
|
|
|
$req = $this->post('/saml2/login');
|
|
|
|
$location = $req->headers->get('Location');
|
2023-04-28 14:54:51 +02:00
|
|
|
return $this->parseSamlDataFromUrl($location, 'SAMLRequest');
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function parseSamlDataFromUrl(string $url, string $paramName): string
|
|
|
|
{
|
|
|
|
$query = explode('?', $url)[1];
|
2021-05-08 14:07:25 +02:00
|
|
|
$params = [];
|
|
|
|
parse_str($query, $params);
|
2021-06-26 17:23:15 +02:00
|
|
|
|
2023-04-28 14:54:51 +02:00
|
|
|
return gzinflate(base64_decode($params[$paramName]));
|
2021-05-08 14:07:25 +02:00
|
|
|
}
|
|
|
|
|
2019-11-17 20:15:37 +01:00
|
|
|
protected function withGet(array $options, callable $callback)
|
|
|
|
{
|
|
|
|
return $this->withGlobal($_GET, $options, $callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function withGlobal(array &$global, array $options, callable $callback)
|
|
|
|
{
|
|
|
|
$original = [];
|
|
|
|
foreach ($options as $key => $val) {
|
|
|
|
$original[$key] = $global[$key] ?? null;
|
|
|
|
$global[$key] = $val;
|
|
|
|
}
|
|
|
|
|
|
|
|
$callback();
|
|
|
|
|
|
|
|
foreach ($options as $key => $val) {
|
|
|
|
$val = $original[$key];
|
|
|
|
if ($val) {
|
|
|
|
$global[$key] = $val;
|
|
|
|
} else {
|
|
|
|
unset($global[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The post data for a callback for single-sign-in.
|
|
|
|
* Provides the following attributes:
|
|
|
|
* array:5 [
|
2021-06-26 17:23:15 +02:00
|
|
|
* "uid" => array:1 [
|
|
|
|
* 0 => "user"
|
|
|
|
* ]
|
|
|
|
* "first_name" => array:1 [
|
|
|
|
* 0 => "Barry"
|
|
|
|
* ]
|
|
|
|
* "last_name" => array:1 [
|
|
|
|
* 0 => "Scott"
|
|
|
|
* ]
|
|
|
|
* "email" => array:1 [
|
|
|
|
* 0 => "user@example.com"
|
|
|
|
* ]
|
|
|
|
* "user_groups" => array:2 [
|
|
|
|
* 0 => "member"
|
|
|
|
* 1 => "admin"
|
|
|
|
* ]
|
|
|
|
* ].
|
2019-11-17 20:15:37 +01:00
|
|
|
*/
|
2023-12-03 20:35:05 +01:00
|
|
|
protected string $acsPostData = 'PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfNGRkNDU2NGRjNzk0MDYxZWYxYmFhMDQ2N2Q3OTAyOGNlZDNjZTU0YmVlIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxOS0xMS0xN1QxNzo1MzozOVoiIERlc3RpbmF0aW9uPSJodHRwOi8vYm9va3N0YWNrLmxvY2FsL3NhbWwyL2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl82YTBmNGYzOTkzMDQwZjE5ODdmZDM3MDY4YjUyOTYyMjlhZDUzNjFjIj48c2FtbDpJc3N1ZXI+aHR0cDovL3NhbWwubG9jYWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNfNGRkNDU2NGRjNzk0MDYxZWYxYmFhMDQ2N2Q3OTAyOGNlZDNjZTU0YmVlIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+dm1oL1M3NU5mK2crZWNESkN6QWJaV0tKVmx1ZzdCZnNDKzlhV05lSXJlUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+dnJhZ0tKWHNjVm5UNjJFaEk3bGk4MERUWHNOTGJOc3lwNWZ2QnU4WjFYSEtFUVA3QWpPNkcxcVBwaGpWQ2dRMzd6TldVVTZvUytQeFA3UDlHeG5xL3hKejRUT3lHcHJ5N1RoK2pIcHc0YWVzQTdrTmp6VU51UmU2c1ltWTlrRXh2VjMvTmJRZjROMlM2Y2RhRHIzWFRodllVVDcxYzQwNVVHOFJpQjJaY3liWHIxZU1yWCtXUDBnU2Qrc0F2RExqTjBJc3pVWlVUNThadFpEVE1ya1ZGL0pIbFBFQ04vVW1sYVBBeitTcUJ4c25xTndZK1oxYUt3MnlqeFRlNnUxM09Kb29OOVN1REowNE0rK2F3RlY3NkI4cXEyTzMxa3FBbDJibm1wTGxtTWdRNFEraUlnL3dCc09abTV1clphOWJObDNLVEhtTVBXbFpkbWhsLzgvMy9IT1RxN2thWGs3cnlWRHRLcFlsZ3FUajNhRUpuL0dwM2o4SFp5MUVialRiOTRRT1ZQMG5IQzB1V2hCaE13TjdzVjFrUSsxU2NjUlpUZXJKSGlSVUQvR0srTVg3M0YrbzJVTFRIL1Z6Tm9SM2o4N2hOLzZ1UC9JeG5aM1RudGR1MFZPZS9ucEdVWjBSMG9SWFhwa2JTL2poNWk1ZjU0RXN4eXZ1VEM5NHdKaEM8L2RzOlNpZ25hdHVyZVZhbHVlPgo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlFYXpDQ0F0T2dBd0lCQWdJVWU3YTA4OENucjRpem1ybkJFbng1cTNIVE12WXdEUVlKS29aSWh2Y05BUUVMQlFBd1JURUxNQWtHQTFVRUJoTUNSMEl4RXpBUkJnTlZCQWdNQ2xOdmJXVXRVM1JoZEdVeElUQWZCZ05WQkFvTUdFbHVkR1Z5Ym1WMElGZHBaR2RwZEhNZ1VIUjVJRXgwWkRBZUZ3MHhPVEV4TVRZeE1qRTNNVFZhRncweU9URXhNVFV4TWpFM01UVmFNRVV4Q3pBSkJnTlZCQVlUQWtkQ01STXdFUVlEVlFRSURBcFRiMjFsTFZOMFlYUmxNU0V3SHdZRFZRUUtEQmhKYm5SbGNtNWxkQ0JYYVdSbmFYUnpJRkIwZVNCTWRHUXdnZ0dpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCandBd2dnR0tBb0lCZ1FEekxlOUZmZHlwbFR4SHA0U3VROWdRdFpUM3QrU0RmdkVMNzJwcENmRlp3NytCNXM1Qi9UNzNhWHBvUTNTNTNwR0kxUklXQ2dlMmlDVVEydHptMjdhU05IMGl1OWFKWWNVUVovUklUcWQwYXl5RGtzMU5BMlBUM1RXNnQzbTdLVjVyZTRQME5iK1lEZXV5SGRreitqY010cG44Q21Cb1QwSCtza2hhMGhpcUlOa2prUlBpSHZMSFZHcCt0SFVFQS9JNm1ONGFCL1VFeFNUTHM3OU5zTFVmdGVxcXhlOSt0dmRVYVRveURQcmhQRmpPTnMrOU5LQ2t6SUM2dmN2N0o2QXR1S0c2bkVUK3pCOXlPV2d0R1lRaWZYcVFBMnk1ZEw4MUJCMHE1dU1hQkxTMnBxM2FQUGp6VTJGMytFeXNqeVNXVG5Da2ZrN0M1U3NDWFJ1OFErVTk1dHVucE5md2Y1b2xFNldhczQ4Tk1NK1B3VjdpQ05NUGtOemxscTZQQ2lNK1A4RHJNU2N6elVaWlFVU3Y2ZFN3UENvK1lTVmltRU0wT2czWEpUaU5oUTVBTmxhSW42Nkt3NWdmb0JmdWlYbXlJS2lTRHlBaURZbUZhZjQzOTV3V3dMa1RSK2N3OFdmamFIc3dLWlRvbW4xTVIzT0pzWTJVSjBlUkJZTStZU3NDQXdFQUFhTlRNRkV3SFFZRFZSME9CQllFRkltcDJDWUNHZmNiN3c5MUgvY1NoVENrWHdSL01COEdBMVVkSXdRWU1CYUFGSW1wMkNZQ0dmY2I3dzkxSC9jU2hUQ2tYd1IvTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3RFFZSktvWklodmNOQVFFTEJRQURnZ0dCQUErZy9DN3VMOWxuK1crcUJrbkxXODFrb2pZZmxnUEsxSTFNSEl3bk12bC9aVEhYNGRSWEtEcms3S2NVcTFLanFhak5WNjZmMWNha3AwM0lpakJpTzBYaTFnWFVaWUxvQ2lOR1V5eXA5WGxvaUl5OVh3MlBpV25ydzAreVp5dlZzc2JlaFhYWUpsNFJpaEJqQld1bDlSNHdNWUxPVVNKRGUyV3hjVUJoSm54eU5ScytQMHhMU1FYNkIybjZueG9Ea280cDA3czhaS1hRa2VpWjJpd0ZkVHh6UmtHanRoTVV2NzA0bnpzVkdCVDBEQ1B0ZlNhTzVLSlpXMXJDczN5aU10aG5CeHE0cUVET1FKRklsKy9MRDcxS2JCOXZaY1c1SnVhdnpCRm1rS0dOcm8vNkcxSTdlbDQ2SVI0d2lqVHlORkNZVXVEOWR0aWduTm1wV3ROOE9XK3B0aUwvanRUeVNXdWtqeXMwcyt2TG44M0NWdmpCMGRKdFZBSV
|
2019-11-17 20:15:37 +01:00
|
|
|
|
2023-12-03 20:35:05 +01:00
|
|
|
protected string $sloResponseData = 'fZHRa8IwEMb/lZJ3bdJa04a2MOYYglOY4sNe5JKms9gmpZfC/vxF3ZjC8OXgLvl938ddjtC1vVjZTzu6d429NaiDr641KC5PBRkHIyxgg8JAp1E4JbZPbysRTanoB+ussi25QR4TgKgH11hDguWiIIeawTxOaK1iPYt5XcczHUlJeVRlMklBJjOuM1qDVCTY6wE9WRAv5HHEUS8NOjDOjyjLJoxNGN+xVESpSNgHCRYaXWPAXaijc70IQ2ntyUPqNG2tgjY8Z45CbNFLmt8V7GxBNuuX1eZ1uT7EcZJKAE4TJhXPaMxlVlFffPKKJnXE5ryusoiU+VlMXJIN5Y/feXRn1VR92GkHFTiY9sc+D2+p/HqRrQM34n33bCsd7KEd9eMd4+W32I5KaUQSlleHP9Hwv6uX3w==';
|
2019-11-17 20:15:37 +01:00
|
|
|
|
2023-12-03 20:35:05 +01:00
|
|
|
protected string $testCert = 'MIIEazCCAtOgAwIBAgIUe7a088Cnr4izmrnBEnx5q3HTMvYwDQYJKoZIhvcNAQELBQAwRTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTExMTYxMjE3MTVaFw0yOTExMTUxMjE3MTVaMEUxCzAJBgNVBAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDzLe9FfdyplTxHp4SuQ9gQtZT3t+SDfvEL72ppCfFZw7+B5s5B/T73aXpoQ3S53pGI1RIWCge2iCUQ2tzm27aSNH0iu9aJYcUQZ/RITqd0ayyDks1NA2PT3TW6t3m7KV5re4P0Nb+YDeuyHdkz+jcMtpn8CmBoT0H+skha0hiqINkjkRPiHvLHVGp+tHUEA/I6mN4aB/UExSTLs79NsLUfteqqxe9+tvdUaToyDPrhPFjONs+9NKCkzIC6vcv7J6AtuKG6nET+zB9yOWgtGYQifXqQA2y5dL81BB0q5uMaBLS2pq3aPPjzU2F3+EysjySWTnCkfk7C5SsCXRu8Q+U95tunpNfwf5olE6Was48NMM+PwV7iCNMPkNzllq6PCiM+P8DrMSczzUZZQUSv6dSwPCo+YSVimEM0Og3XJTiNhQ5ANlaIn66Kw5gfoBfuiXmyIKiSDyAiDYmFaf4395wWwLkTR+cw8WfjaHswKZTomn1MR3OJsY2UJ0eRBYM+YSsCAwEAAaNTMFEwHQYDVR0OBBYEFImp2CYCGfcb7w91H/cShTCkXwR/MB8GA1UdIwQYMBaAFImp2CYCGfcb7w91H/cShTCkXwR/MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggGBAA+g/C7uL9ln+W+qBknLW81kojYflgPK1I1MHIwnMvl/ZTHX4dRXKDrk7KcUq1KjqajNV66f1cakp03IijBiO0Xi1gXUZYLoCiNGUyyp9XloiIy9Xw2PiWnrw0+yZyvVssbehXXYJl4RihBjBWul9R4wMYLOUSJDe2WxcUBhJnxyNRs+P0xLSQX6B2n6nxoDko4p07s8ZKXQkeiZ2iwFdTxzRkGjthMUv704nzsVGBT0DCPtfSaO5KJZW1rCs3yiMthnBxq4qEDOQJFIl+/LD71KbB9vZcW5JuavzBFmkKGNro/6G1I7el46IR4wijTyNFCYUuD9dtignNmpWtN8OW+ptiL/jtTySWukjys0s+vLn83CVvjB0dJtVAIYOgXFdIuii66gczwwM/LGiOExJn0dTNzsJ/IYhpxL4FBEuP0pskY0o0aUlJ2LS2j+wSQTRKsBgMjyrUrekle2ODStStn3eabjIx0/FHlpFr0jNIm/oMP7kwjtUX4zaNe47QI4Gg==';
|
2019-11-17 14:26:43 +01:00
|
|
|
}
|