Passed
Push — master ( 7e81b0...7eb007 )
by Robbie
12:39 queued 11s
created

testCanRemoveTheOnlyMethodWhenMFAIsOptional()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 7
rs 10
1
<?php
2
3
namespace SilverStripe\MFA\Tests\Service;
4
5
use SilverStripe\Core\Config\Config;
6
use SilverStripe\Core\Injector\Injector;
7
use SilverStripe\Dev\SapphireTest;
8
use SilverStripe\MFA\BackupCode\Method as BackupCodeMethod;
9
use SilverStripe\MFA\Extension\MemberExtension;
10
use SilverStripe\MFA\Model\RegisteredMethod;
11
use SilverStripe\MFA\Service\EnforcementManager;
12
use SilverStripe\MFA\Service\MethodRegistry;
13
use SilverStripe\MFA\Service\RegisteredMethodManager;
14
use SilverStripe\MFA\Tests\Stub\BasicMath\Method as BasicMathMethod;
15
use SilverStripe\ORM\DataObject;
16
use SilverStripe\Security\Member;
17
use SilverStripe\View\SSViewer;
18
19
class RegisteredMethodManagerTest extends SapphireTest
20
{
21
    protected static $fixture_file = 'RegisteredMethodManagerTest.yml';
22
23
    protected static $required_extensions = [
24
        Member::class => [
25
            MemberExtension::class,
26
        ],
27
    ];
28
29
    public function testGetFromMember()
30
    {
31
        /** @var Member $member */
32
        $member = $this->objFromFixture(Member::class, 'sally_smith');
33
34
        $result = RegisteredMethodManager::singleton()->getFromMember($member, new BackupCodeMethod());
35
        $this->assertInstanceOf(RegisteredMethod::class, $result);
36
    }
37
38
    public function testGetFromMemberReturnsNullWhenNotFound()
39
    {
40
        /** @var Member $member */
41
        $member = $this->objFromFixture(Member::class, 'sally_smith');
42
43
        $result = RegisteredMethodManager::singleton()->getFromMember($member, new BasicMathMethod());
44
        $this->assertNull($result);
45
    }
46
47
    public function testRegisterForMemberWritesToExistingRegisteredMethod()
48
    {
49
        /** @var Member&MemberExtension $member */
50
        $member = $this->objFromFixture(Member::class, 'sally_smith');
51
        $method = new BackupCodeMethod();
52
53
        $this->assertCount(1, $member->RegisteredMFAMethods());
54
        RegisteredMethodManager::singleton()->registerForMember($member, $method, ['foo' => 'bar']);
55
56
        $this->assertCount(1, $member->RegisteredMFAMethods());
57
        $this->assertSame(['foo' => 'bar'], json_decode($member->RegisteredMFAMethods()->first()->Data, true));
0 ignored issues
show
Bug introduced by
The method first() does not exist on Countable. It seems like you code against a sub-type of Countable such as SilverStripe\ORM\SS_List. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

57
        $this->assertSame(['foo' => 'bar'], json_decode($member->RegisteredMFAMethods()->/** @scrutinizer ignore-call */ first()->Data, true));
Loading history...
Bug introduced by
The method first() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as IntlCodePointBreakIterator or IntlRuleBasedBreakIterator or IntlBreakIterator or SilverStripe\ORM\SS_List or SilverStripe\View\ViewableData or SilverStripe\ORM\Connect\Query. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

57
        $this->assertSame(['foo' => 'bar'], json_decode($member->RegisteredMFAMethods()->/** @scrutinizer ignore-call */ first()->Data, true));
Loading history...
58
    }
59
60
    public function testRegisterForMemberCreatesNewMethod()
61
    {
62
        /** @var Member&MemberExtension $member */
63
        $member = Member::create(['FirstName' => 'Mike']);
64
        $member->write();
65
        $method = new BackupCodeMethod();
66
67
        $this->assertCount(0, $member->RegisteredMFAMethods());
68
        RegisteredMethodManager::singleton()->registerForMember($member, $method, ['foo', 'bar']);
69
        $this->assertCount(1, $member->RegisteredMFAMethods());
70
    }
71
72
    public function testRegisterForMemberAssignsDefaultRegisteredMethod()
73
    {
74
        /** @var Member&MemberExtension $member */
75
        $member = Member::create(['FirstName' => 'Mike']);
76
        $member->write();
77
        $method = new BackupCodeMethod();
78
79
        RegisteredMethodManager::singleton()->registerForMember($member, $method, ['foo', 'bar']);
80
        $this->assertCount(1, $member->RegisteredMFAMethods());
81
        $defaultMethod = $member->getDefaultRegisteredMethod();
82
        $this->assertNotNull($defaultMethod, 'Default registered method should have been assigned');
83
84
        $newMethod = new BasicMathMethod();
85
        RegisteredMethodManager::singleton()->registerForMember($member, $newMethod, ['foo', 'baz']);
86
        $this->assertCount(2, $member->RegisteredMFAMethods());
87
        $this->assertSame(
88
            $defaultMethod->ID,
89
            $member->getDefaultRegisteredMethod()->ID,
90
            'Default registered method should not have changed'
91
        );
92
    }
93
94
    public function testRegisterForMemberSendsNotification()
95
    {
96
        SSViewer::set_themes(['$public', '$default']);
97
        /** @var Member&MemberExtension $member */
98
        $member = Member::create(['FirstName' => 'Mike', 'Email' => '[email protected]']);
99
        $member->write();
100
        $method = new BasicMathMethod();
101
102
        $manager = RegisteredMethodManager::singleton();
0 ignored issues
show
Unused Code introduced by
The assignment to $manager is dead and can be removed.
Loading history...
103
        RegisteredMethodManager::singleton()->registerForMember($member, $method, ['foo', 'bar']);
104
105
        $this->assertEmailSent(
106
            $member->Email,
107
            null,
108
            '/method was added to your account/',
109
            '/You have successfully registered/'
110
        );
111
    }
112
113
    public function testRegisterBackupMethodDoesNotSendEmail()
114
    {
115
        /** @var Member&MemberExtension $member */
116
        $member = Member::create(['FirstName' => 'Mike', 'Email' => '[email protected]']);
117
        $member->write();
118
        $method = new BackupCodeMethod();
119
120
        $manager = RegisteredMethodManager::singleton();
0 ignored issues
show
Unused Code introduced by
The assignment to $manager is dead and can be removed.
Loading history...
121
        RegisteredMethodManager::singleton()->registerForMember($member, $method, ['foo', 'bar']);
122
123
        $this->assertNull($this->findEmail($member->Email));
124
    }
125
126
127
    public function testRegisterForMemberDoesNothingWithNoData()
128
    {
129
        /** @var Member&MemberExtension $member */
130
        $member = Member::create(['FirstName' => 'Michelle']);
131
        $member->write();
132
        $method = new BackupCodeMethod();
133
134
        $this->assertCount(0, $member->RegisteredMFAMethods());
135
        RegisteredMethodManager::singleton()->registerForMember($member, $method, []);
136
        $this->assertCount(0, $member->RegisteredMFAMethods());
137
    }
138
139
    public function testDeleteFromMember()
140
    {
141
        /** @var Member&MemberExtension $member */
142
        $member = $this->objFromFixture(Member::class, 'bob_jones');
143
144
        // Bob has 3 methods
145
        $this->assertCount(3, $member->RegisteredMFAMethods());
146
147
        $manager = RegisteredMethodManager::singleton();
148
        $manager->deleteFromMember($member, new BasicMathMethod());
149
150
        $this->assertCount(2, $member->RegisteredMFAMethods());
151
        $this->assertNull($manager->getFromMember($member, new BasicMathMethod()));
152
    }
153
154
    public function testDeleteFromMemberSendsNotification()
155
    {
156
        SSViewer::set_themes(['$public', '$default']);
157
        /** @var Member&MemberExtension $member */
158
        $member = $this->objFromFixture(Member::class, 'bob_jones');
159
160
        $manager = RegisteredMethodManager::singleton();
161
        $manager->deleteFromMember($member, new BasicMathMethod());
162
163
        $this->assertEmailSent($member->Email, null, '/method was removed from your account/', '/You have removed/');
164
    }
165
166
    public function testDeletingLastMethodRemovesBackupCodes()
167
    {
168
        // Assert the config is set for backup codes
169
        Config::modify()->set(MethodRegistry::class, 'default_backup_method', BackupCodeMethod::class);
170
171
        /** @var Member&MemberExtension $member */
172
        $member = $this->objFromFixture(Member::class, 'jane_doe');
173
174
        $manager = RegisteredMethodManager::singleton();
175
        $this->assertCount(2, $member->RegisteredMFAMethods());
176
        // Get methods from the member to assert orphan records are removed
177
        $backupMethod = $manager->getFromMember($member, new BackupCodeMethod());
178
        $mathMethod = $manager->getFromMember($member, new BasicMathMethod());
179
180
        $this->assertNotNull($backupMethod);
181
        $this->assertNotNull($mathMethod);
182
183
        $manager->deleteFromMember($member, new BasicMathMethod());
184
185
        $this->assertCount(0, $member->RegisteredMFAMethods());
186
187
        $this->assertNull(DataObject::get_by_id(RegisteredMethod::class, $backupMethod->ID));
188
        $this->assertNull(DataObject::get_by_id(RegisteredMethod::class, $mathMethod->ID));
189
    }
190
191
    public function testCanRemoveTheOnlyMethodWhenMFAIsOptional()
192
    {
193
        Config::modify()->set(MethodRegistry::class, 'default_backup_method', BackupCodeMethod::class);
194
        $this->registerServiceForMFAToBeRequired(false);
195
        $jane = $this->objFromFixture(Member::class, 'jane_doe');
196
        $method = $this->objFromFixture(RegisteredMethod::class, 'math');
197
        $this->assertTrue(RegisteredMethodManager::create()->canRemoveMethod($jane, $method->getMethod()));
198
    }
199
200
    public function testCannotRemoveTheOnlyMethodWhenMFAIsRequired()
201
    {
202
        Config::modify()->set(MethodRegistry::class, 'default_backup_method', BackupCodeMethod::class);
203
        $this->registerServiceForMFAToBeRequired(true);
204
        $jane = $this->objFromFixture(Member::class, 'jane_doe');
205
        $method = $this->objFromFixture(RegisteredMethod::class, 'math');
206
        $this->assertFalse(RegisteredMethodManager::create()->canRemoveMethod($jane, $method->getMethod()));
207
    }
208
209
    public function testCanRemoveOneOfTwoMethodsWhenMFAIsRequired()
210
    {
211
        Config::modify()->set(MethodRegistry::class, 'default_backup_method', BackupCodeMethod::class);
212
        $this->registerServiceForMFAToBeRequired(true);
213
        $bob = $this->objFromFixture(Member::class, 'bob_jones');
214
        $method = $this->objFromFixture(RegisteredMethod::class, 'math2');
215
        $this->assertTrue(RegisteredMethodManager::create()->canRemoveMethod($bob, $method->getMethod()));
216
    }
217
218
    private function registerServiceForMFAToBeRequired($required = false)
219
    {
220
        $enforcementMock = $this->getMockBuilder(EnforcementManager::class)
221
            ->setMethods(['isMFARequired'])
222
            ->getMock();
223
        $enforcementMock
224
            ->method('isMFARequired')
225
            ->willReturn($required);
226
        Injector::inst()->registerService($enforcementMock, EnforcementManager::class);
227
    }
228
}
229