LoginHandlerTest   A
last analyzed

Complexity

Total Complexity 7

Size/Duplication

Total Lines 143
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 74
c 1
b 0
f 0
dl 0
loc 143
rs 10
wmc 7

5 Methods

Rating   Name   Duplication   Size   Complexity  
A testGetSetService() 0 7 1
A testInstantiate() 0 6 1
A setUp() 0 20 1
A testDoLogin() 0 82 3
A tearDown() 0 5 1
1
<?php
2
3
namespace Firesphere\HaveIBeenPwned\Tests;
4
5
use Firesphere\HaveIBeenPwned\Controllers\LoginHandler;
6
use Firesphere\HaveIBeenPwned\Models\HaveIBeenPwnedPage;
7
use Firesphere\HaveIBeenPwned\Services\HaveIBeenPwnedService;
8
use GuzzleHttp\Handler\MockHandler;
9
use GuzzleHttp\Psr7\Response;
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Control\HTTPRequest;
12
use SilverStripe\Control\Session;
13
use SilverStripe\Core\Config\Config;
14
use SilverStripe\Core\Environment;
15
use SilverStripe\Core\Injector\Injector;
16
use SilverStripe\Dev\SapphireTest;
17
use SilverStripe\Security\Authenticator;
18
use SilverStripe\Security\DefaultAdminService;
19
use SilverStripe\Security\IdentityStore;
20
use SilverStripe\Security\Member;
21
use SilverStripe\Security\MemberAuthenticator\LoginHandler as BaseLoginHandler;
22
use SilverStripe\Security\MemberAuthenticator\LostPasswordForm;
23
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;
24
use SilverStripe\Security\MemberAuthenticator\MemberLoginForm;
25
use SilverStripe\Security\PasswordValidator;
26
use SilverStripe\Security\Security;
27
28
class LoginHandlerTest extends SapphireTest
29
{
30
    /**
31
     * @var LoginHandler
32
     */
33
    protected $handler;
34
35
    /**
36
     * @var MemberAuthenticator
37
     */
38
    protected $authenticator;
39
40
    protected $memberId;
41
42
    public function testInstantiate()
43
    {
44
        $this->assertInstanceOf(LoginHandler::class, $this->handler);
45
        $this->assertNotSame(BaseLoginHandler::class, get_class($this->handler));
46
47
        $this->assertInstanceOf(HaveIBeenPwnedService::class, $this->handler->getService());
48
    }
49
50
    public function testGetSetService()
51
    {
52
        $service = Injector::inst()->get(HaveIBeenPwnedService::class);
53
        $response = $this->handler->setService($service);
54
55
        $this->assertInstanceOf(HaveIBeenPwnedService::class, $this->handler->getService());
56
        $this->assertInstanceOf(LoginHandler::class, $response);
57
    }
58
59
    public function testDoLogin()
60
    {
61
        $body = file_get_contents(__DIR__ . '/../fixtures/pwnd123.txt');
62
        // This sets up the mock client to respond to the request it gets
63
        // with an HTTP 200 containing your mock body.
64
        $mock = new MockHandler([
65
            new Response(200, [], $body),
66
            new Response(200, [], $body),
67
            new Response(200, [], $body),
68
            new Response(200, [], $body),
69
            new Response(200, [], $body),
70
        ]);
71
72
        $this->handler->getService()->setArgs(['handler' => $mock]);
73
74
        $form = MemberLoginForm::create(Controller::curr(), get_class($this->authenticator), 'LoginForm');
75
        /** @var HTTPRequest $request */
76
        $request = Injector::inst()->createWithArgs(HTTPRequest::class, ['GET', '/login']);
77
        $request->setSession(Injector::inst()->createWithArgs(Session::class, [['bla' => 'bla']]));
78
        $this->handler->setRequest($request);
79
80
        // Login allowed
81
        $response = $this->handler->doLogin(['Email' => '[email protected]', 'Password' => '1234567890'], $form, $request);
82
83
        $this->assertEquals(302, $response->getStatusCode());
84
        $this->assertNotContains('lostpassword', $response->getHeader('location'));
85
86
        Config::modify()->set(HaveIBeenPwnedService::class, 'allow_pwnd', false);
87
88
        // Login with breached is not allowed
89
        $response = $this->handler->doLogin(['Email' => '[email protected]', 'Password' => '1234567890'], $form, $request);
90
91
        $this->assertEquals(302, $response->getStatusCode());
92
        $this->assertContains('lostpassword', $response->getHeader('location'));
93
        /** @var Member $member */
94
        $member = Member::get()->byID($this->memberId);
95
96
        // Password should be properly expired
97
        $this->assertEquals('1970-01-01', $member->PasswordExpiry);
98
        // The password is now null, but can't be tested due to salting
99
        Injector::inst()->get(IdentityStore::class)->logOut();
100
101
        // Login with non-breached password
102
        $response = $this->handler->doLogin(['Email' => '[email protected]', 'Password' => '12345678'], $form, $request);
103
104
        $this->assertEquals(302, $response->getStatusCode());
105
        $this->assertNotContains('lostpassword', $response->getHeader('location'));
106
        Injector::inst()->get(IdentityStore::class)->logOut();
107
108
        // Login with non-existing member
109
        $response = $this->handler->doLogin(
110
            ['Email' => '[email protected]', 'Password' => '1234567890'],
111
            $form,
112
            $request
113
        );
114
115
        $this->assertEquals(302, $response->getStatusCode());
116
        $this->assertContains('lostpassword', $response->getHeader('location'));
117
118
        $passwordForm = LostPasswordForm::create($this->handler, Authenticator::class, 'lostPasswordForm');
119
120
        $this->assertContains('You can read more here', $passwordForm->getMessage());
121
122
        // Default Admin is always allowed
123
        $admin = Environment::getEnv('SS_DEFAULT_ADMIN_USERNAME');
124
125
        $password = Environment::getEnv('SS_DEFAULT_ADMIN_PASSWORD');
126
        $this->assertEquals(302, $response->getStatusCode());
127
128
        $this->assertNotContains('lostpassword', $response->getHeader('location'));
129
        //don't run the test if default admin or password are missing
130
        $member = Security::getCurrentUser();
131
        if (!$admin || !$password) {
132
            $this->assertTrue(DefaultAdminService::isDefaultAdmin($member->Email));
133
            $this->markTestSkipped();
134
        } else {
135
            $response = $this->handler->doLogin(['Email' => $admin, 'Password' => $password], $form, $request);
136
137
            $this->assertEquals(302, $response->getStatusCode());
138
            $this->assertNotContains('lostpassword', $response->getHeader('location'));
139
            $member = Security::getCurrentUser();
140
            $this->assertTrue(DefaultAdminService::isDefaultAdmin($member->Email));
141
        }
142
    }
143
144
    protected function setUp()
145
    {
146
        Config::modify()->set(HaveIBeenPwnedService::class, 'allow_pwnd', true);
147
        Config::modify()->set(HaveIBeenPwnedService::class, 'save_pwnd', false);
148
        // This is about HaveIBeenPwned, not actual password strength
149
        $validator = new PasswordValidator();
150
151
        $validator->setMinLength(0);
152
        $validator->setHistoricCount(0);
153
        Member::set_password_validator($validator);
154
155
        HaveIBeenPwnedPage::create(['Title' => 'I am pwnd'])->write();
156
157
        $member = Member::create(['Email' => '[email protected]', 'Password' => '1234567890']);
158
        $this->memberId = $member->write();
159
        $this->authenticator = Injector::inst()->get(MemberAuthenticator::class);
160
        /** @var LoginHandler $handler */
161
        $this->handler = Injector::inst()->createWithArgs(LoginHandler::class, ['', $this->authenticator]);
162
163
        return parent::setUp();
164
    }
165
166
    protected function tearDown()
167
    {
168
        Member::get()->byID($this->memberId)->delete();
169
        HaveIBeenPwnedPage::get()->filter(['Title' => 'I am pwnd'])->first()->delete();
170
        parent::tearDown();
171
    }
172
}
173