ChangePasswordHandlerTest   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 119
c 3
b 0
f 0
dl 0
loc 231
rs 10
wmc 14

14 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 7 1
A testMFADoesNotLoadWhenAUserDoesNotHaveRegisteredMethods() 0 10 1
A doLogin() 0 12 1
A testMFADoesNotLoadWhenAUserIsLoggedIn() 0 5 1
A testVerifyMfaReturnsWhenVerificationIsNotComplete() 0 17 1
A testMfaStartsVerificationResponse() 0 20 1
A testGetSchema() 0 17 1
A testMfaRedirectsBackWithoutMember() 0 6 1
A testStartMfaCheckThrowsExceptionWithoutStore() 0 11 1
A testVerifyMfaResultSuccessful() 0 20 1
A testVerifyMfaCheckReturnsForbiddenOnVerificationFailure() 0 22 1
A testStartMfaReturnsForbiddenWithoutMember() 0 15 1
A testVerifyMfaCheckReturnsUnsuccessfulValidationResult() 0 18 1
A testMFALoadsWhenAUserHasConfiguredMethods() 0 10 1
1
<?php
2
3
namespace SilverStripe\MFA\Tests\Authenticator;
4
5
use PHPUnit_Framework_MockObject_MockObject;
6
use Psr\Log\LoggerInterface;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Control\HTTPResponse;
9
use SilverStripe\Control\Session;
10
use SilverStripe\Core\Config\Config;
11
use SilverStripe\Dev\FunctionalTest;
12
use SilverStripe\MFA\Authenticator\ChangePasswordHandler;
13
use SilverStripe\MFA\Authenticator\MemberAuthenticator;
14
use SilverStripe\MFA\Exception\InvalidMethodException;
15
use SilverStripe\MFA\Extension\MemberExtension;
16
use SilverStripe\MFA\Service\MethodRegistry;
17
use SilverStripe\MFA\State\Result;
18
use SilverStripe\MFA\Store\SessionStore;
19
use SilverStripe\MFA\Store\StoreInterface;
20
use SilverStripe\MFA\Tests\Stub\BasicMath\Method;
21
use SilverStripe\Security\Member;
22
use SilverStripe\SiteConfig\SiteConfig;
23
24
class ChangePasswordHandlerTest extends FunctionalTest
25
{
26
    protected static $fixture_file = 'ChangePasswordHandlerTest.yml';
27
28
    protected function setUp()
29
    {
30
        parent::setUp();
31
32
        Config::modify()
33
            ->set(MethodRegistry::class, 'methods', [Method::class])
34
            ->set(Member::class, 'auto_login_token_lifetime', 10);
35
    }
36
37
    /**
38
     * @param Member $member
39
     * @param string $password
40
     * @return HTTPResponse
41
     */
42
    protected function doLogin(Member $member, $password)
43
    {
44
        $this->get('Security/changepassword');
45
46
        return $this->submitForm(
47
            'MemberLoginForm_LoginForm',
48
            null,
49
            [
50
                'Email' => $member->Email,
51
                'Password' => $password,
52
                'AuthenticationMethod' => MemberAuthenticator::class,
53
                'action_doLogin' => 1,
54
            ]
55
        );
56
    }
57
58
    public function testMFADoesNotLoadWhenAUserIsLoggedIn()
59
    {
60
        $this->logInAs('simon');
61
        $response = $this->get('Security/changepassword');
62
        $this->assertContains('OldPassword', $response->getBody());
63
    }
64
65
    public function testMFADoesNotLoadWhenAUserDoesNotHaveRegisteredMethods()
66
    {
67
        /** @var Member&MemberExtension $member */
68
        $member = $this->objFromFixture(Member::class, 'guy');
69
        $memberId = $member->ID;
70
        $token = $member->generateAutologinTokenAndStoreHash();
71
        $response = $this->get("Security/changepassword?m={$memberId}&t={$token}");
72
73
        $this->assertContains('NewPassword1', $response->getBody(), 'There should be a new password field');
74
        $this->assertContains('NewPassword2', $response->getBody(), 'There should be a confirm new password field');
75
    }
76
77
    public function testMFALoadsWhenAUserHasConfiguredMethods()
78
    {
79
        /** @var Member&MemberExtension $member */
80
        $member = $this->objFromFixture(Member::class, 'robbie');
81
        $memberId = $member->ID;
82
        $token = $member->generateAutologinTokenAndStoreHash();
83
        $response = $this->get("Security/changepassword?m={$memberId}&t={$token}");
84
85
        $this->assertNotContains('type="password"', $response->getBody(), 'Password form should be circumvented');
86
        $this->assertContains('id="mfa-app"', $response->getBody(), 'MFA screen should be displayed');
87
    }
88
89
    public function testGetSchema()
90
    {
91
        /** @var Member&MemberExtension $member */
92
        $member = $this->objFromFixture(Member::class, 'robbie');
93
        $memberId = $member->ID;
94
        $token = $member->generateAutologinTokenAndStoreHash();
95
        $this->get("Security/changepassword?m={$memberId}&t={$token}");
96
97
        $response = $this->get('Security/changepassword/mfa/schema');
98
99
        $this->assertSame('application/json', $response->getHeader('Content-Type'));
100
        $schema = json_decode($response->getBody(), true);
101
102
        $this->assertArrayHasKey('endpoints', $schema);
103
        $this->assertArrayHasKey('verify', $schema['endpoints']);
104
        $this->assertArrayHasKey('complete', $schema['endpoints']);
105
        $this->assertFalse($schema['shouldRedirect']);
106
    }
107
108
    public function testMfaRedirectsBackWithoutMember()
109
    {
110
        $this->autoFollowRedirection = false;
111
        $response = $this->get('Security/changepassword/mfa');
112
113
        $this->assertEquals(302, $response->getStatusCode());
114
    }
115
116
    /**
117
     * @expectedException \LogicException
118
     * @expectedExceptionMessage Store not found, please create one first.
119
     */
120
    public function testStartMfaCheckThrowsExceptionWithoutStore()
121
    {
122
        /** @var ChangePasswordHandler&PHPUnit_Framework_MockObject_MockObject $handler */
123
        $handler = $this->getMockBuilder(ChangePasswordHandler::class)
124
            ->disableOriginalConstructor()
125
            ->setMethods(['getStore'])
126
            ->getMock();
127
128
        $handler->expects($this->once())->method('getStore')->willReturn(null);
129
130
        $handler->startMFACheck($this->createMock(HTTPRequest::class));
131
    }
132
133
    public function testStartMfaReturnsForbiddenWithoutMember()
134
    {
135
        /** @var ChangePasswordHandler&PHPUnit_Framework_MockObject_MockObject $handler */
136
        $handler = $this->getMockBuilder(ChangePasswordHandler::class)
137
            ->disableOriginalConstructor()
138
            ->setMethods(['getStore'])
139
            ->getMock();
140
141
        $store = $this->createMock(StoreInterface::class);
142
143
        $handler->expects($this->once())->method('getStore')->willReturn($store);
144
        $store->expects($this->once())->method('getMember')->willReturn(null);
145
146
        $response = $handler->startMFACheck($this->createMock(HTTPRequest::class));
147
        $this->assertEquals(403, $response->getStatusCode());
148
    }
149
150
    public function testMfaStartsVerificationResponse()
151
    {
152
        /** @var ChangePasswordHandler&PHPUnit_Framework_MockObject_MockObject $handler */
153
        $handler = $this->getMockBuilder(ChangePasswordHandler::class)
154
            ->disableOriginalConstructor()
155
            ->setMethods(['getStore', 'createStartVerificationResponse'])
156
            ->getMock();
157
158
        $store = $this->createMock(StoreInterface::class);
159
160
        $handler->expects($this->once())->method('getStore')->willReturn($store);
161
        $store->expects($this->once())->method('getMember')->willReturn($this->createMock(Member::class));
162
163
        $expectedResponse = new HTTPResponse();
164
        $handler->expects($this->once())->method('createStartVerificationResponse')->willReturn($expectedResponse);
165
166
        $request = new HTTPRequest('GET', '');
167
        $request->setRouteParams(['Method' => 'test']);
168
        $response = $handler->startMFACheck($request);
169
        $this->assertSame($expectedResponse, $response);
170
    }
171
172
    public function testVerifyMfaCheckReturnsForbiddenOnVerificationFailure()
173
    {
174
        /** @var ChangePasswordHandler&PHPUnit_Framework_MockObject_MockObject $handler */
175
        $handler = $this->getMockBuilder(ChangePasswordHandler::class)
176
            ->disableOriginalConstructor()
177
            ->setMethods(['getStore', 'completeVerificationRequest'])
178
            ->getMock();
179
180
        /** @var LoggerInterface&PHPUnit_Framework_MockObject_MockObject $logger */
181
        $logger = $this->createMock(LoggerInterface::class);
182
        $logger->expects($this->once())->method('debug');
183
        $handler->setLogger($logger);
184
185
        $store = $this->createMock(StoreInterface::class);
186
187
        $handler->expects($this->once())->method('getStore')->willReturn($store);
188
        $handler->expects($this->once())->method('completeVerificationRequest')->willThrowException(
189
            new InvalidMethodException('foo')
190
        );
191
192
        $response = $handler->verifyMFACheck(new HTTPRequest('GET', ''));
193
        $this->assertEquals(403, $response->getStatusCode());
194
    }
195
196
    public function testVerifyMfaCheckReturnsUnsuccessfulValidationResult()
197
    {
198
        /** @var ChangePasswordHandler&PHPUnit_Framework_MockObject_MockObject $handler */
199
        $handler = $this->getMockBuilder(ChangePasswordHandler::class)
200
            ->disableOriginalConstructor()
201
            ->setMethods(['getStore', 'completeVerificationRequest'])
202
            ->getMock();
203
204
        $store = $this->createMock(StoreInterface::class);
205
        $handler->expects($this->once())->method('getStore')->willReturn($store);
206
207
        $handler->expects($this->once())->method('completeVerificationRequest')->willReturn(
208
            new Result(false, 'It is a test')
209
        );
210
211
        $response = $handler->verifyMFACheck(new HTTPRequest('GET', ''));
212
        $this->assertEquals(401, $response->getStatusCode());
213
        $this->assertContains('It is a test', $response->getBody());
214
    }
215
216
    public function testVerifyMfaReturnsWhenVerificationIsNotComplete()
217
    {
218
        /** @var ChangePasswordHandler&PHPUnit_Framework_MockObject_MockObject $handler */
219
        $handler = $this->getMockBuilder(ChangePasswordHandler::class)
220
            ->disableOriginalConstructor()
221
            ->setMethods(['getStore', 'completeVerificationRequest', 'isVerificationComplete'])
222
            ->getMock();
223
224
        $store = $this->createMock(StoreInterface::class);
225
226
        $handler->expects($this->once())->method('getStore')->willReturn($store);
227
        $handler->expects($this->once())->method('completeVerificationRequest')->willReturn(new Result());
228
        $handler->expects($this->once())->method('isVerificationComplete')->willReturn(false);
229
230
        $response = $handler->verifyMFACheck(new HTTPRequest('GET', ''));
231
        $this->assertEquals(202, $response->getStatusCode());
232
        $this->assertContains('Additional authentication required', $response->getBody());
233
    }
234
235
    public function testVerifyMfaResultSuccessful()
236
    {
237
        /** @var ChangePasswordHandler&PHPUnit_Framework_MockObject_MockObject $handler */
238
        $handler = $this->getMockBuilder(ChangePasswordHandler::class)
239
            ->disableOriginalConstructor()
240
            ->setMethods(['getStore', 'completeVerificationRequest', 'isVerificationComplete'])
241
            ->getMock();
242
243
        $store = new SessionStore($this->createMock(Member::class));
244
245
        $handler->expects($this->once())->method('getStore')->willReturn($store);
246
        $handler->expects($this->once())->method('completeVerificationRequest')->willReturn(new Result());
247
        $handler->expects($this->once())->method('isVerificationComplete')->willReturn(true);
248
249
        $request = new HTTPRequest('GET', '/');
250
        $request->setSession(new Session([]));
251
        $response = $handler->verifyMFACheck($request);
252
253
        $this->assertEquals(200, $response->getStatusCode());
254
        $this->assertContains('Multi-factor authenticated', $response->getBody());
255
    }
256
}
257