This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace SilverStripe\SpellCheck\Tests; |
||
4 | |||
5 | use SilverStripe\Control\Session; |
||
6 | use SilverStripe\Core\Config\Config; |
||
7 | use SilverStripe\Core\Injector\Injector; |
||
8 | use SilverStripe\Dev\FunctionalTest; |
||
9 | use SilverStripe\Security\RandomGenerator; |
||
10 | use SilverStripe\Security\SecurityToken; |
||
11 | use SilverStripe\SpellCheck\Data\SpellProvider; |
||
12 | use SilverStripe\SpellCheck\Handling\SpellController; |
||
13 | use SilverStripe\SpellCheck\Tests\Stub\SpellProviderStub; |
||
14 | |||
15 | /** |
||
16 | * Tests the {@see SpellController} class |
||
17 | */ |
||
18 | class SpellControllerTest extends FunctionalTest |
||
19 | { |
||
20 | protected $usesDatabase = true; |
||
21 | |||
22 | protected $securityWasEnabled = false; |
||
23 | |||
24 | protected function setUp() |
||
25 | { |
||
26 | parent::setUp(); |
||
27 | |||
28 | $this->securityWasEnabled = SecurityToken::is_enabled(); |
||
29 | |||
30 | // Reset config |
||
31 | Config::modify()->set(SpellController::class, 'required_permission', 'CMS_ACCESS_CMSMain'); |
||
32 | Config::inst()->remove(SpellController::class, 'locales'); |
||
0 ignored issues
–
show
|
|||
33 | Config::modify() |
||
34 | ->set(SpellController::class, 'locales', array('en_US', 'en_NZ', 'fr_FR')) |
||
35 | ->set(SpellController::class, 'enable_security_token', true) |
||
36 | ->set(SpellController::class, 'return_errors_as_ok', false); |
||
37 | |||
38 | SecurityToken::enable(); |
||
39 | |||
40 | // Setup mock for testing provider |
||
41 | $spellChecker = new SpellProviderStub; |
||
42 | Injector::inst()->registerService($spellChecker, SpellProvider::class); |
||
43 | } |
||
44 | |||
45 | protected function tearDown() |
||
46 | { |
||
47 | if ($this->securityWasEnabled) { |
||
48 | SecurityToken::enable(); |
||
49 | } else { |
||
50 | SecurityToken::disable(); |
||
51 | } |
||
52 | |||
53 | parent::tearDown(); |
||
54 | } |
||
55 | |||
56 | /** |
||
57 | * Tests security ID check |
||
58 | */ |
||
59 | public function testSecurityID() |
||
60 | { |
||
61 | // Mock token |
||
62 | $securityToken = SecurityToken::inst(); |
||
63 | $generator = new RandomGenerator(); |
||
64 | $token = $generator->randomToken('sha1'); |
||
65 | $session = array( |
||
66 | $securityToken->getName() => $token |
||
67 | ); |
||
68 | $tokenError = _t( |
||
69 | 'SilverStripe\\SpellCheck\\Handling\\SpellController.SecurityMissing', |
||
70 | 'Your session has expired. Please refresh your browser to continue.' |
||
71 | ); |
||
72 | |||
73 | // Test request sans token |
||
74 | $response = $this->get('spellcheck', Injector::inst()->create(Session::class, $session)); |
||
75 | $this->assertEquals(400, $response->getStatusCode()); |
||
76 | $jsonBody = json_decode($response->getBody()); |
||
77 | $this->assertEquals($tokenError, $jsonBody->error); |
||
78 | |||
79 | // Test request with correct token (will fail with an unrelated error) |
||
80 | $response = $this->get( |
||
81 | 'spellcheck/?SecurityID='.urlencode($token), |
||
82 | Injector::inst()->create(Session::class, $session) |
||
83 | ); |
||
84 | $jsonBody = json_decode($response->getBody()); |
||
85 | $this->assertNotEquals($tokenError, $jsonBody->error); |
||
86 | |||
87 | // Test request with check disabled |
||
88 | Config::modify()->set(SpellController::class, 'enable_security_token', false); |
||
89 | $response = $this->get('spellcheck', Injector::inst()->create(Session::class, $session)); |
||
90 | $jsonBody = json_decode($response->getBody()); |
||
91 | $this->assertNotEquals($tokenError, $jsonBody->error); |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * Tests permission check |
||
96 | */ |
||
97 | public function testPermissions() |
||
98 | { |
||
99 | // Disable security ID for this test |
||
100 | Config::modify()->set(SpellController::class, 'enable_security_token', false); |
||
101 | $securityError = _t('SilverStripe\\SpellCheck\\Handling\\SpellController.SecurityDenied', 'Permission Denied'); |
||
102 | |||
103 | // Test admin permissions |
||
104 | Config::modify()->set(SpellController::class, 'required_permission', 'ADMIN'); |
||
105 | $this->logInWithPermission('ADMIN'); |
||
106 | $response = $this->get('spellcheck'); |
||
107 | $jsonBody = json_decode($response->getBody()); |
||
108 | $this->assertNotEquals($securityError, $jsonBody->error); |
||
109 | |||
110 | // Test insufficient permissions |
||
111 | $this->logInWithPermission('CMS_ACCESS_CMSMain'); |
||
112 | $response = $this->get('spellcheck'); |
||
113 | $this->assertEquals(403, $response->getStatusCode()); |
||
114 | $jsonBody = json_decode($response->getBody()); |
||
115 | $this->assertEquals($securityError, $jsonBody->error); |
||
116 | |||
117 | // Test disabled permissions |
||
118 | Config::modify()->set(SpellController::class, 'required_permission', false); |
||
119 | $response = $this->get('spellcheck'); |
||
120 | $jsonBody = json_decode($response->getBody()); |
||
121 | $this->assertNotEquals($securityError, $jsonBody->error); |
||
122 | } |
||
123 | |||
124 | /** |
||
125 | * @param string $lang |
||
126 | * @param int $expectedStatusCode |
||
127 | * @dataProvider langProvider |
||
128 | */ |
||
129 | public function testBothLangAndLocaleInputResolveToLocale($lang, $expectedStatusCode, $errorsAreOk = false) |
||
130 | { |
||
131 | $this->logInWithPermission('ADMIN'); |
||
132 | Config::modify() |
||
133 | ->set(SpellController::class, 'enable_security_token', false) |
||
134 | ->set(SpellController::class, 'return_errors_as_ok', $errorsAreOk); |
||
135 | |||
136 | $mockData = [ |
||
137 | 'ajax' => true, |
||
138 | 'method' => 'spellcheck', |
||
139 | 'lang' => $lang, |
||
140 | 'text' => 'Collor is everywhere', |
||
141 | ]; |
||
142 | $response = $this->post('spellcheck', $mockData); |
||
143 | $this->assertEquals($expectedStatusCode, $response->getStatusCode()); |
||
144 | } |
||
145 | |||
146 | /** |
||
147 | * @return array[] |
||
148 | */ |
||
149 | public function langProvider() |
||
150 | { |
||
151 | return [ |
||
152 | 'english_language' => [ |
||
153 | 'en', // assumes en_US is the default locale for "en" language |
||
154 | 200, |
||
155 | ], |
||
156 | 'english_locale' => [ |
||
157 | 'en_NZ', |
||
158 | 200, |
||
159 | ], |
||
160 | 'invalid_language' => [ |
||
161 | 'ru', |
||
162 | 400, |
||
163 | ], |
||
164 | 'invalid_language_returned_as_ok' => [ |
||
165 | 'ru', |
||
166 | 200, |
||
167 | true |
||
168 | ], |
||
169 | 'other_valid_language' => [ |
||
170 | 'fr', // assumes fr_FR is the default locale for "en" language |
||
171 | 200, |
||
172 | ], |
||
173 | 'other_valid_locale' => [ |
||
174 | 'fr_FR', |
||
175 | 200, |
||
176 | ], |
||
177 | ]; |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * Ensure that invalid input is correctly rejected |
||
182 | */ |
||
183 | public function testInputRejection() |
||
184 | { |
||
185 | // Disable security ID and permissions for this test |
||
186 | Config::modify()->set(SpellController::class, 'enable_security_token', false); |
||
187 | Config::modify()->set(SpellController::class, 'required_permission', false); |
||
188 | $invalidRequest = _t('SilverStripe\\SpellCheck\\Handling\\SpellController.InvalidRequest', 'Invalid request'); |
||
189 | |||
190 | // Test spellcheck acceptance |
||
191 | $mockData = [ |
||
192 | 'method' => 'spellcheck', |
||
193 | 'lang' => 'en_NZ', |
||
194 | 'text' => 'Collor is everywhere', |
||
195 | ]; |
||
196 | $response = $this->post('spellcheck', ['ajax' => true] + $mockData); |
||
197 | $this->assertEquals(200, $response->getStatusCode()); |
||
198 | $jsonBody = json_decode($response->getBody()); |
||
199 | $this->assertNotEmpty($jsonBody->words); |
||
200 | $this->assertNotEmpty($jsonBody->words->collor); |
||
201 | $this->assertEquals(['collar', 'colour'], $jsonBody->words->collor); |
||
202 | |||
203 | // Test non-ajax rejection |
||
204 | $response = $this->post('spellcheck', $mockData); |
||
205 | $this->assertEquals(400, $response->getStatusCode()); |
||
206 | $jsonBody = json_decode($response->getBody()); |
||
207 | $this->assertEquals($invalidRequest, $jsonBody->error); |
||
208 | |||
209 | // Test incorrect method |
||
210 | $dataInvalidMethod = $mockData; |
||
211 | $dataInvalidMethod['method'] = 'validate'; |
||
212 | $response = $this->post('spellcheck', ['ajax' => true] + $dataInvalidMethod); |
||
213 | $this->assertEquals(400, $response->getStatusCode()); |
||
214 | $jsonBody = json_decode($response->getBody()); |
||
215 | $this->assertEquals( |
||
216 | _t( |
||
217 | 'SilverStripe\\SpellCheck\\Handling\\SpellController.UnsupportedMethod', |
||
218 | "Unsupported method '{method}'", |
||
219 | array('method' => 'validate') |
||
220 | ), |
||
221 | $jsonBody->error |
||
222 | ); |
||
223 | |||
224 | // Test missing method |
||
225 | $dataNoMethod = $mockData; |
||
226 | unset($dataNoMethod['method']); |
||
227 | $response = $this->post('spellcheck', ['ajax' => true] + $dataNoMethod); |
||
228 | $this->assertEquals(400, $response->getStatusCode()); |
||
229 | $jsonBody = json_decode($response->getBody()); |
||
230 | $this->assertEquals($invalidRequest, $jsonBody->error); |
||
231 | |||
232 | // Test unsupported locale |
||
233 | $dataWrongLocale = $mockData; |
||
234 | $dataWrongLocale['lang'] = 'de_DE'; |
||
235 | |||
236 | $response = $this->post('spellcheck', ['ajax' => true] + $dataWrongLocale); |
||
237 | $this->assertEquals(400, $response->getStatusCode()); |
||
238 | $jsonBody = json_decode($response->getBody()); |
||
239 | $this->assertEquals(_t( |
||
240 | 'SilverStripe\\SpellCheck\\Handling\\SpellController.InvalidLocale', |
||
241 | 'Not a supported locale' |
||
242 | ), $jsonBody->error); |
||
243 | } |
||
244 | } |
||
245 |
Let’s take a look at an example:
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the interface: