-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathSecurityControllerTest.php
More file actions
163 lines (134 loc) · 6.34 KB
/
Copy pathSecurityControllerTest.php
File metadata and controls
163 lines (134 loc) · 6.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
<?php
/*
* Copyright (c) 2025-2026 Netresearch DTT GmbH
* SPDX-License-Identifier: AGPL-3.0-only
*/
declare(strict_types=1);
namespace Tests\Controller;
use RuntimeException;
use Tests\AbstractWebTestCase;
use function assert;
/**
* @internal
*
* @coversNothing
*/
final class SecurityControllerTest extends AbstractWebTestCase
{
/**
* Override setUp to not automatically log in for this test class.
*
* @phpstan-ignore phpunit.callParent (Intentionally bypassing parent to avoid auto-login)
*/
protected function setUp(): void
{
// Call grandparent setUp to skip the automatic login in AbstractWebTestCase
\Symfony\Bundle\FrameworkBundle\Test\WebTestCase::setUp();
// Initialize HTTP client (from HttpClientTrait)
$this->initializeHttpClient();
// Initialize database and transactions (from DatabaseTestTrait)
$this->initializeDatabase();
// DO NOT call logInSession() - we want to test unauthenticated access
}
public function testAccessToProtectedRouteReturnsForbidden(): void
{
// Session is already cleared in setUp, just verify we're not logged in
$session = $this->client->getContainer()->get('session');
assert($session instanceof \Symfony\Component\HttpFoundation\Session\SessionInterface);
$session->clear();
$session->save();
// Also clear the security token to ensure full logout in test env
if ($this->client->getContainer()->has('security.token_storage')) {
$tokenStorage = $this->client->getContainer()->get('security.token_storage');
assert($tokenStorage instanceof \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface);
$tokenStorage->setToken(null);
}
// Try to access a protected route with only text/html accept header
// Use an admin route that requires authentication
$this->client->request(
\Symfony\Component\HttpFoundation\Request::METHOD_GET,
'/getAllUsers',
[],
[],
[
'HTTP_ACCEPT' => 'text/html',
'HTTP_USER_AGENT' => 'Mozilla/5.0',
],
);
// Admin routes redirect to login for unauthenticated users with HTML accept header
$this->assertStatusCode(302);
}
public function testLoggedInUserCanAccessProtectedRoute(): void
{
// Use the Base class login functionality to authenticate
$this->logInSession('i.myself');
// Try to access a simple protected route
$this->client->request(\Symfony\Component\HttpFoundation\Request::METHOD_GET, '/getUsers');
// Should succeed with 200 status
$this->assertStatusCode(200);
}
public function testLoginPageRendersCorrectly(): void
{
// Ensure kernel booted in setUp (if any) is shut down before creating a new client
self::ensureKernelShutdown();
$kernelBrowser = self::createClient();
// Use the crawler provided by the client request
$kernelBrowser->request(\Symfony\Component\HttpFoundation\Request::METHOD_GET, '/login');
self::assertResponseIsSuccessful(); // Asserts 2xx status code
$content = (string) $kernelBrowser->getResponse()->getContent();
// The login is now a SolidJS app (login.tsx) mounted on #login, with the
// config injected for the client and a server-rendered no-JS fallback form.
self::assertStringContainsString('id="login"', $content);
self::assertStringContainsString('window.LOGIN_CONFIG', $content);
// The fallback form (and the SolidJS form) use the firewall field names.
self::assertStringContainsString('name="_username"', $content);
self::assertStringContainsString('name="_password"', $content);
self::assertStringContainsString('name="_csrf_token"', $content);
self::assertStringContainsString('action="/login"', $content);
// ExtJS is no longer loaded on the login page.
self::assertStringNotContainsString('Ext.form.Panel', $content);
self::assertStringNotContainsString('ext-all.js', $content);
}
#[\PHPUnit\Framework\Attributes\Group('network')]
public function testLogoutClearsAuthenticationAndReturnsForbidden(): void
{
// When a user is logged in
$this->logInSession('unittest');
// They should be able to access a protected route
$this->client->request(\Symfony\Component\HttpFoundation\Request::METHOD_GET, '/getUsers');
$this->assertStatusCode(200);
// Get CSRF token for logout (required with CSRF protection enabled)
if (null === $this->serviceContainer) {
throw new RuntimeException('Service container not initialized');
}
$csrfTokenManager = $this->serviceContainer->get('security.csrf.token_manager');
assert($csrfTokenManager instanceof \Symfony\Component\Security\Csrf\CsrfTokenManagerInterface);
$csrfToken = $csrfTokenManager->getToken('logout')->getValue();
// After logging out with CSRF token. The stateless CSRF validator
// accepts the token only alongside a same-origin fetch-metadata
// header, which BrowserKit does not send on its own.
$this->client->request(
\Symfony\Component\HttpFoundation\Request::METHOD_GET,
'/logout',
['_csrf_token' => $csrfToken],
[],
['HTTP_SEC_FETCH_SITE' => 'same-origin'],
);
// The user should be redirected
self::assertTrue($this->client->getResponse()->isRedirect());
// Ensure token cleared to avoid sticky authentication across requests
$tokenStorage = $this->client->getContainer()->get('security.token_storage');
assert($tokenStorage instanceof \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface);
self::assertNull($tokenStorage->getToken());
// Try to access a protected route again with browser-like headers
$this->client->request(
\Symfony\Component\HttpFoundation\Request::METHOD_GET,
'/getUsers',
[],
[],
['HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'],
);
// Should redirect to login when not authenticated
$this->assertStatusCode(302);
}
}