silverstripe /
silverstripe-spellcheck
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: