Completed
Push — master ( 036b0a...0ab8bd )
by Garion
15s queued 11s
created

testSetDefaultRegisteredMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 10
c 2
b 0
f 0
nc 1
nop 0
dl 0
loc 17
rs 9.9332
1
<?php
2
3
namespace SilverStripe\MFA\Tests\Controller;
4
5
use PHPUnit_Framework_MockObject_MockObject;
6
use Psr\Log\LoggerInterface;
7
use SilverStripe\Admin\AdminRootController;
8
use SilverStripe\Control\Controller;
9
use SilverStripe\Control\HTTPRequest;
10
use SilverStripe\Core\Config\Config;
11
use SilverStripe\Core\Injector\Injector;
12
use SilverStripe\Dev\FunctionalTest;
13
use SilverStripe\MFA\Controller\AdminRegistrationController;
14
use SilverStripe\MFA\Extension\MemberExtension;
15
use SilverStripe\MFA\Model\RegisteredMethod;
16
use SilverStripe\MFA\Service\MethodRegistry;
17
use SilverStripe\MFA\Service\RegisteredMethodManager;
18
use SilverStripe\MFA\State\AvailableMethodDetails;
19
use SilverStripe\MFA\Store\SessionStore;
20
use SilverStripe\MFA\Tests\Stub\BasicMath\Method as BasicMathMethod;
21
use SilverStripe\ORM\ValidationException;
22
use SilverStripe\Security\Member;
23
use SilverStripe\Security\Security;
24
use SilverStripe\Security\SecurityToken;
25
use SilverStripe\SecurityExtensions\Service\SudoModeServiceInterface;
26
27
class AdminRegistrationControllerTest extends FunctionalTest
28
{
29
    protected static $fixture_file = 'AdminRegistrationControllerTest.yml';
30
31
    protected function setUp()
32
    {
33
        parent::setUp();
34
35
        MethodRegistry::config()->set('methods', [
36
            BasicMathMethod::class,
37
        ]);
38
39
        /** @var SudoModeServiceInterface&PHPUnit_Framework_MockObject_MockObject $sudoModeService */
40
        $sudoModeService = $this->createMock(SudoModeServiceInterface::class);
41
        $sudoModeService->expects($this->any())->method('check')->willReturn(true);
42
        Injector::inst()->registerService($sudoModeService, SudoModeServiceInterface::class);
43
    }
44
45
    public function testStartRegistrationAssertsValidMethod()
46
    {
47
        $this->logInAs($this->objFromFixture(Member::class, 'sally_smith'));
48
49
        $result = $this->get(Controller::join_links(AdminRootController::admin_url(), 'mfa', 'register/foo'));
50
51
        $this->assertSame(400, $result->getStatusCode());
52
        $this->assertContains('No such method is available', $result->getBody());
53
    }
54
55
    public function testStartRegistrationEnforcesSudoMode()
56
    {
57
        $this->logInAs($this->objFromFixture(Member::class, 'sally_smith'));
58
59
        /** @var SudoModeServiceInterface&PHPUnit_Framework_MockObject_MockObject $sudoModeService */
60
        $sudoModeService = $this->createMock(SudoModeServiceInterface::class);
61
        $sudoModeService->expects($this->any())->method('check')->willReturn(false);
62
        Injector::inst()->registerService($sudoModeService, SudoModeServiceInterface::class);
63
64
        $result = $this->get(Controller::join_links(AdminRootController::admin_url(), 'mfa', 'register/foo'));
65
66
        $this->assertSame(400, $result->getStatusCode());
67
        $this->assertContains('Invalid session. Please refresh and try again.', (string) $result->getBody());
68
    }
69
70
    public function testStartRegistrationReturns200Response()
71
    {
72
        $this->logInAs($this->objFromFixture(Member::class, 'sally_smith'));
73
        $method = new BasicMathMethod();
74
75
        $result = $this->get(
76
            Controller::join_links(
77
                AdminRootController::admin_url(),
78
                'mfa',
79
                'register',
80
                $method->getURLSegment()
81
            )
82
        );
83
84
        $this->assertSame(200, $result->getStatusCode());
85
    }
86
87
    public function testFinishRegistrationGracefullyHandlesInvalidSessions()
88
    {
89
        $this->logInAs($this->objFromFixture(Member::class, 'sally_smith'));
90
        $method = new BasicMathMethod();
91
92
        $result = $this->post(
93
            Controller::join_links(
94
                AdminRootController::admin_url(),
95
                'mfa',
96
                'register',
97
                $method->getURLSegment()
98
            ),
99
            ['dummy' => 'data']
100
        );
101
102
        $this->assertSame(400, $result->getStatusCode());
103
        $this->assertContains('Invalid session', $result->getBody());
104
    }
105
106
    public function testFinishRegistrationAssertsValidMethod()
107
    {
108
        /** @var Member $member */
109
        $member = $this->objFromFixture(Member::class, 'sally_smith');
110
        $this->logInAs($member);
111
        $method = new BasicMathMethod();
112
113
        $store = new SessionStore($member);
114
        $store->setMethod($method->getURLSegment());
115
        $this->session()->set(SessionStore::SESSION_KEY, $store);
116
117
        $result = $this->post(
118
            Controller::join_links(
119
                AdminRootController::admin_url(),
120
                'mfa',
121
                'register',
122
                'foo'
123
            ),
124
            ['dummy' => 'data']
125
        );
126
127
        $this->assertSame(400, $result->getStatusCode());
128
        $this->assertContains('No such method is available', $result->getBody());
129
    }
130
131
    public function testFinishRegistrationCompletesWhenValid()
132
    {
133
        /** @var Member $member */
134
        $member = $this->objFromFixture(Member::class, 'sally_smith');
135
        $this->logInAs($member);
136
        $method = new BasicMathMethod();
137
138
        $store = new SessionStore($member);
139
        $store->setMethod($method->getURLSegment());
140
        $this->session()->set(SessionStore::SESSION_KEY, $store);
141
142
        $result = $this->post(
143
            Controller::join_links(
144
                AdminRootController::admin_url(),
145
                'mfa',
146
                'register',
147
                $method->getURLSegment()
148
            ),
149
            ['dummy' => 'data'],
150
            null,
151
            $this->session(),
152
            json_encode(['number' => 7])
153
        );
154
155
        $this->assertSame(201, $result->getStatusCode());
156
    }
157
158
    public function testRemoveRegistrationChecksCSRF()
159
    {
160
        SecurityToken::enable();
161
162
        $controller = new AdminRegistrationController();
163
        $request = new HTTPRequest('DELETE', '');
164
        $response = $controller->removeRegisteredMethod($request);
165
166
        $this->assertSame(400, $response->getStatusCode());
167
        $this->assertContains('Request timed out', $response->getBody());
168
169
        $token = SecurityToken::inst();
170
        $request = new HTTPRequest('DELETE', '', [$token->getName() => $token->getValue()]);
171
172
        $response = $controller->removeRegisteredMethod($request);
173
174
        $this->assertNotContains('Request timed out', $response->getBody());
175
    }
176
177
    public function testRemoveRegistrationRequiresMethod()
178
    {
179
        $this->logInWithPermission();
180
181
        // Prep a mock for deleting methods
182
        $registeredMethodManager = $this->scaffoldRegisteredMethodManagerMock();
183
184
        $controller = new AdminRegistrationController();
185
186
        // Method not even provided
187
        $request = new HTTPRequest('DELETE', '');
188
        $response = $controller->removeRegisteredMethod($request);
189
190
        $this->assertSame(400, $response->getStatusCode());
191
        $this->assertContains('No such method is available', $response->getBody());
192
193
        // Method provided but non-existing
194
        $request = new HTTPRequest('DELETE', '');
195
        $request->setRouteParams(['Method' => 'fake123']);
196
        $response = $controller->removeRegisteredMethod($request);
197
198
        $this->assertSame(400, $response->getStatusCode());
199
        $this->assertContains('No such method is available', $response->getBody());
200
201
        // Existing method
202
        $request = new HTTPRequest('DELETE', '');
203
        $basicMathMethod = new BasicMathMethod();
204
        $request->setRouteParams(['Method' => $basicMathMethod->getURLSegment()]);
205
        $registeredMethodManager->expects($this->once())->method('deleteFromMember')->willReturn(true);
206
        $response = $controller->removeRegisteredMethod($request);
207
208
        $this->assertSame(200, $response->getStatusCode());
209
        $this->assertTrue(json_decode($response->getBody())->success);
210
    }
211
212
    public function testRemoveRegistrationSuccessIsReflectedInResponse()
213
    {
214
        $this->logInWithPermission();
215
216
        // Prep a mock for deleting methods
217
        $registeredMethodManager = $this->scaffoldRegisteredMethodManagerMock();
218
219
        $controller = new AdminRegistrationController();
220
221
        $request = new HTTPRequest('DELETE', '');
222
        $basicMathMethod = new BasicMathMethod();
223
        $request->setRouteParams(['Method' => $basicMathMethod->getURLSegment()]);
224
225
        $registeredMethodManager->expects($this->exactly(2))->method('deleteFromMember')->willReturn(true, false);
226
227
        $response = $controller->removeRegisteredMethod($request);
228
229
        $this->assertSame(200, $response->getStatusCode());
230
        $this->assertTrue(json_decode($response->getBody())->success);
231
232
        $response = $controller->removeRegisteredMethod($request);
233
234
        $this->assertSame(400, $response->getStatusCode());
235
        $this->assertContains('Could not delete the specified method from the user', $response->getBody());
236
    }
237
238
    public function testRemoveRegistrationSuccessResponseIncludesTheNowAvailableMethod()
239
    {
240
        $this->logInWithPermission();
241
242
        // Prep a mock for deleting methods
243
        $registeredMethodManager = $this->scaffoldRegisteredMethodManagerMock();
244
245
        $controller = new AdminRegistrationController();
246
247
        $request = new HTTPRequest('DELETE', '');
248
        $basicMathMethod = new BasicMathMethod();
249
        $request->setRouteParams(['Method' => $basicMathMethod->getURLSegment()]);
250
        $registeredMethodManager->expects($this->once())->method('deleteFromMember')->willReturn(true);
251
252
        $expectedAvailableMethod = new AvailableMethodDetails($basicMathMethod);
253
254
        $response = $controller->removeRegisteredMethod($request);
255
256
        $this->assertSame(
257
            $expectedAvailableMethod->jsonSerialize(),
258
            json_decode($response->getBody(), true)['availableMethod']
259
        );
260
    }
261
262
    public function testRemoveRegistrationSuccessIndicatesIfTheBackupMethodIsRegistered()
263
    {
264
        $this->logInWithPermission();
265
266
        // Prep a mock for deleting methods
267
        $registeredMethodManager = $this->scaffoldRegisteredMethodManagerMock();
268
269
        $controller = new AdminRegistrationController();
270
271
        $request = new HTTPRequest('DELETE', '');
272
        $basicMathMethod = new BasicMathMethod();
273
        $request->setRouteParams(['Method' => $basicMathMethod->getURLSegment()]);
274
        $registeredMethodManager->expects($this->any())->method('deleteFromMember')->willReturn(true);
275
276
        // Test when there's no backup method registered
277
        Config::modify()->set(MethodRegistry::class, 'default_backup_method', null);
278
        $response = $controller->removeRegisteredMethod($request);
279
        $this->assertFalse(json_decode($response->getBody())->hasBackupMethod);
280
281
        // Make "basic math" the backup method as it's the only available method
282
        Config::modify()->set(MethodRegistry::class, 'default_backup_method', BasicMathMethod::class);
283
284
        // Mock checking for the registered backup method when it's not registered (first) and then when it is (second)
285
        $registeredMethodManager
286
            ->expects($this->exactly(2))
287
            ->method('getFromMember')
288
            ->willReturn(null, new RegisteredMethod());
289
290
        $response = $controller->removeRegisteredMethod($request);
291
        $this->assertFalse(json_decode($response->getBody())->hasBackupMethod);
292
        $response = $controller->removeRegisteredMethod($request);
293
        $this->assertTrue(json_decode($response->getBody())->hasBackupMethod);
294
    }
295
296
    protected function scaffoldRegisteredMethodManagerMock()
297
    {
298
        $mock = $this->createMock(RegisteredMethodManager::class);
299
        Injector::inst()->registerService($mock, RegisteredMethodManager::class);
300
301
        return $mock;
302
    }
303
304
    public function testEnforcesSudoMode()
305
    {
306
        $sudoModeService = $this->createMock(SudoModeServiceInterface::class);
307
        $sudoModeService->expects($this->any())->method('check')->willReturn(false);
308
        Injector::inst()->registerService($sudoModeService, SudoModeServiceInterface::class);
309
310
        /** @var Member $member */
311
        $member = $this->objFromFixture(Member::class, 'sally_smith');
312
        $this->logInAs($member);
313
        $method = new BasicMathMethod();
314
315
        $store = new SessionStore($member);
316
        $store->setMethod($method->getURLSegment());
317
        $this->session()->set(SessionStore::SESSION_KEY, $store);
318
319
        $result = $this->post(
320
            Controller::join_links(
321
                AdminRootController::admin_url(),
322
                'mfa',
323
                'register',
324
                $method->getURLSegment()
325
            ),
326
            ['dummy' => 'data'],
327
            null,
328
            $this->session(),
329
            json_encode(['number' => 7])
330
        );
331
332
        $this->assertSame(400, $result->getStatusCode());
333
        $this->assertContains('Invalid session', $result->getBody());
334
    }
335
336
    /**
337
     * This controller allows any logged in user to access it, since its methods have their own permission
338
     * check validation already.
339
     *
340
     * See: https://github.com/silverstripe/silverstripe-mfa/issues/171
341
     */
342
    public function testAnyUserCanView()
343
    {
344
        $this->assertFalse(AdminRegistrationController::getRequiredPermissions());
345
    }
346
347
    public function testSetDefaultRegisteredMethodChecksCSRF()
348
    {
349
        SecurityToken::enable();
350
351
        $request = new HTTPRequest('POST', '');
352
        $request->setSession($this->session());
353
        $controller = new AdminRegistrationController();
354
355
        $response = $controller->setDefaultRegisteredMethod($request);
356
357
        $this->assertSame(400, $response->getStatusCode());
358
        $this->assertContains('Request timed out', $response->getBody());
359
360
        $token = SecurityToken::inst();
361
        $request = new HTTPRequest('POST', '', [$token->getName() => $token->getValue()]);
362
        $request->setSession($this->session());
363
364
        $response = $controller->setDefaultRegisteredMethod($request);
365
        $this->assertNotContains('Request timed out', $response->getBody());
366
    }
367
368
    public function testSetDefaultRegisteredMethodFailsWhenMethodWasNotFound()
369
    {
370
        $request = new HTTPRequest('POST', '');
371
        $request->setRouteParams(['Method' => 'doesnotexist']);
372
        $request->setSession($this->session());
373
        $controller = new AdminRegistrationController();
374
375
        $response = $controller->setDefaultRegisteredMethod($request);
376
377
        $this->assertSame(400, $response->getStatusCode());
378
        $this->assertContains('No such method is available', $response->getBody());
379
    }
380
381
    public function testSetDefaultRegisteredMethodFailsWhenRegisteredMethodWasNotFoundForUser()
382
    {
383
        // Arbitrary user, no MFA configured for it
384
        $this->logInWithPermission();
385
386
        $request = new HTTPRequest('POST', '');
387
        $basicMathMethod = new BasicMathMethod();
388
        $request->setRouteParams(['Method' => $basicMathMethod->getURLSegment()]);
389
        $request->setSession($this->session());
390
        $controller = new AdminRegistrationController();
391
392
        $response = $controller->setDefaultRegisteredMethod($request);
393
394
        $this->assertSame(400, $response->getStatusCode());
395
        $this->assertContains('No such registered method is available', $response->getBody());
396
    }
397
398
    public function testSetDefaultRegisteredMethodHandlesExceptionsOnWrite()
399
    {
400
        $registeredMethodManager = $this->scaffoldRegisteredMethodManagerMock();
401
402
        $controller = new AdminRegistrationController();
403
        /** @var LoggerInterface&PHPUnit_Framework_MockObject_MockObject $loggerMock */
404
        $loggerMock = $this->createMock(LoggerInterface::class);
405
        $controller->setLogger($loggerMock);
406
407
        $request = new HTTPRequest('POST', '');
408
        $request->setSession($this->session());
409
        $basicMathMethod = new BasicMathMethod();
410
        $request->setRouteParams(['Method' => $basicMathMethod->getURLSegment()]);
411
412
        $registeredMethodManager
413
            ->expects($this->once())
414
            ->method('getFromMember')
415
            ->willReturn(new RegisteredMethod());
416
417
        /** @var Member&PHPUnit_Framework_MockObject_MockObject $memberMock */
418
        $memberMock = $this->createMock(Member::class);
419
        $memberMock->method('write')->willThrowException(new ValidationException());
420
        Security::setCurrentUser($memberMock);
421
422
        $loggerMock->expects($this->once())->method('debug');
423
        $response = $controller->setDefaultRegisteredMethod($request);
424
        $this->assertSame(400, $response->getStatusCode());
425
        $this->assertContains('Could not set the default method for the user', $response->getBody());
426
    }
427
428
    public function testSetDefaultRegisteredMethod()
429
    {
430
        /** @var Member&MemberExtension $member */
431
        $member = $this->objFromFixture(Member::class, 'sally_smith');
432
        $this->logInAs($member);
433
        // Give Sally basic math
434
        $basicMathMethod = new BasicMathMethod();
435
        RegisteredMethodManager::singleton()->registerForMember($member, $basicMathMethod, ['foo' => 'bar']);
436
437
        // Set basic math as the default method
438
        $controller = new AdminRegistrationController();
439
        $request = new HTTPRequest('POST', '');
440
        $request->setSession($this->session());
441
        $request->setRouteParams(['Method' => $basicMathMethod->getURLSegment()]);
442
443
        $response = $controller->setDefaultRegisteredMethod($request);
444
        $this->assertSame(200, $response->getStatusCode());
445
    }
446
}
447