Completed
Push — master ( 8f56fe...31d5ab )
by Robbie
12s
created

ChangePasswordHandlerTest::testGetSchema()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 17
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 0
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
        SiteConfig::current_site_config()->update(['MFAEnabled' => true])->write();
37
    }
38
39
    /**
40
     * @param Member $member
41
     * @param string $password
42
     * @return HTTPResponse
43
     */
44
    protected function doLogin(Member $member, $password)
45
    {
46
        $this->get('Security/changepassword');
47
48
        return $this->submitForm(
49
            'MemberLoginForm_LoginForm',
50
            null,
51
            [
52
                'Email' => $member->Email,
53
                'Password' => $password,
54
                'AuthenticationMethod' => MemberAuthenticator::class,
55
                'action_doLogin' => 1,
56
            ]
57
        );
58
    }
59
60
    public function testMFADoesNotLoadWhenAUserIsLoggedIn()
61
    {
62
        $this->logInAs('simon');
63
        $response = $this->get('Security/changepassword');
64
        $this->assertContains('OldPassword', $response->getBody());
65
    }
66
67
    public function testMFADoesNotLoadWhenAUserDoesNotHaveRegisteredMethods()
68
    {
69
        /** @var Member&MemberExtension $member */
70
        $member = $this->objFromFixture(Member::class, 'guy');
71
        $memberId = $member->ID;
72
        $token = $member->generateAutologinTokenAndStoreHash();
73
        $response = $this->get("Security/changepassword?m={$memberId}&t={$token}");
74
75
        $this->assertContains('NewPassword1', $response->getBody(), 'There should be a new password field');
76
        $this->assertContains('NewPassword2', $response->getBody(), 'There should be a confirm new password field');
77
    }
78
79
    public function testMFALoadsWhenAUserHasConfiguredMethods()
80
    {
81
        /** @var Member&MemberExtension $member */
82
        $member = $this->objFromFixture(Member::class, 'robbie');
83
        $memberId = $member->ID;
84
        $token = $member->generateAutologinTokenAndStoreHash();
85
        $response = $this->get("Security/changepassword?m={$memberId}&t={$token}");
86
87
        $this->assertNotContains('type="password"', $response->getBody(), 'Password form should be circumvented');
88
        $this->assertContains('id="mfa-app"', $response->getBody(), 'MFA screen should be displayed');
89
    }
90
91
    public function testGetSchema()
92
    {
93
        /** @var Member&MemberExtension $member */
94
        $member = $this->objFromFixture(Member::class, 'robbie');
95
        $memberId = $member->ID;
96
        $token = $member->generateAutologinTokenAndStoreHash();
97
        $this->get("Security/changepassword?m={$memberId}&t={$token}");
98
99
        $response = $this->get('Security/changepassword/mfa/schema');
100
101
        $this->assertSame('application/json', $response->getHeader('Content-Type'));
102
        $schema = json_decode($response->getBody(), true);
103
104
        $this->assertArrayHasKey('endpoints', $schema);
105
        $this->assertArrayHasKey('verify', $schema['endpoints']);
106
        $this->assertArrayHasKey('complete', $schema['endpoints']);
107
        $this->assertFalse($schema['shouldRedirect']);
108
    }
109
110
    public function testMfaRedirectsBackWithoutMember()
111
    {
112
        $this->autoFollowRedirection = false;
113
        $response = $this->get('Security/changepassword/mfa');
114
115
        $this->assertEquals(302, $response->getStatusCode());
116
    }
117
118
    /**
119
     * @expectedException \LogicException
120
     * @expectedExceptionMessage Store not found, please create one first.
121
     */
122
    public function testStartMfaCheckThrowsExceptionWithoutStore()
123
    {
124
        /** @var ChangePasswordHandler&PHPUnit_Framework_MockObject_MockObject $handler */
125
        $handler = $this->getMockBuilder(ChangePasswordHandler::class)
126
            ->disableOriginalConstructor()
127
            ->setMethods(['getStore'])
128
            ->getMock();
129
130
        $handler->expects($this->once())->method('getStore')->willReturn(null);
131
132
        $handler->startMFACheck($this->createMock(HTTPRequest::class));
133
    }
134
135
    public function testStartMfaReturnsForbiddenWithoutMember()
136
    {
137
        /** @var ChangePasswordHandler&PHPUnit_Framework_MockObject_MockObject $handler */
138
        $handler = $this->getMockBuilder(ChangePasswordHandler::class)
139
            ->disableOriginalConstructor()
140
            ->setMethods(['getStore'])
141
            ->getMock();
142
143
        $store = $this->createMock(StoreInterface::class);
144
145
        $handler->expects($this->once())->method('getStore')->willReturn($store);
146
        $store->expects($this->once())->method('getMember')->willReturn(null);
147
148
        $response = $handler->startMFACheck($this->createMock(HTTPRequest::class));
149
        $this->assertEquals(403, $response->getStatusCode());
150
    }
151
152
    public function testMfaStartsVerificationResponse()
153
    {
154
        /** @var ChangePasswordHandler&PHPUnit_Framework_MockObject_MockObject $handler */
155
        $handler = $this->getMockBuilder(ChangePasswordHandler::class)
156
            ->disableOriginalConstructor()
157
            ->setMethods(['getStore', 'createStartVerificationResponse'])
158
            ->getMock();
159
160
        $store = $this->createMock(StoreInterface::class);
161
162
        $handler->expects($this->once())->method('getStore')->willReturn($store);
163
        $store->expects($this->once())->method('getMember')->willReturn($this->createMock(Member::class));
164
165
        $expectedResponse = new HTTPResponse();
166
        $handler->expects($this->once())->method('createStartVerificationResponse')->willReturn($expectedResponse);
167
168
        $response = $handler->startMFACheck($this->createMock(HTTPRequest::class));
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($this->createMock(HTTPRequest::class));
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($this->createMock(HTTPRequest::class));
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($this->createMock(HTTPRequest::class));
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