Passed
Pull Request — 4.10 (#10333)
by Guy
06:12
created

GroupTest::testSettingCodeRepeatedly()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 11
nc 1
nop 0
dl 0
loc 18
rs 9.9
c 1
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Security\Tests;
4
5
use InvalidArgumentException;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Dev\FunctionalTest;
8
use SilverStripe\Forms\RequiredFields;
9
use SilverStripe\ORM\ArrayList;
10
use SilverStripe\ORM\DataObject;
11
use SilverStripe\Security\Group;
12
use SilverStripe\Security\Member;
13
use SilverStripe\Security\Permission;
14
use SilverStripe\Security\Tests\GroupTest\TestMember;
15
16
class GroupTest extends FunctionalTest
17
{
18
    protected static $fixture_file = 'GroupTest.yml';
19
20
    protected static $extra_dataobjects = [
21
        TestMember::class
22
    ];
23
24
    protected function setUp(): void
25
    {
26
        parent::setUp();
27
    }
28
29
    public function testGroupCodeDefaultsToTitle()
30
    {
31
        $g1 = new Group();
32
        $g1->Title = "My Title";
33
        $g1->write();
34
        $this->assertEquals('my-title', $g1->Code, 'Custom title gets converted to code if none exists already');
35
36
        $g2 = new Group();
37
        $g2->Title = "My Title and Code";
38
        $g2->Code = "my-code";
39
        $g2->write();
40
        $this->assertEquals('my-code', $g2->Code, 'Custom attributes are not overwritten by Title field');
41
42
        $g3 = new Group();
43
        $g3->Title = _t('SilverStripe\\Admin\\SecurityAdmin.NEWGROUP', "New Group");
44
        $g3->write();
45
        $this->assertNull($g3->Code, 'Default title doesnt trigger attribute setting');
46
    }
47
48
    /**
49
     * @skipUpgrade
50
     */
51
    public function testMemberGroupRelationForm()
52
    {
53
        $this->logInAs($this->idFromFixture(TestMember::class, 'admin'));
54
55
        $adminGroup = $this->objFromFixture(Group::class, 'admingroup');
56
        $parentGroup = $this->objFromFixture(Group::class, 'parentgroup');
57
58
        // Test single group relation through checkboxsetfield
59
        $form = new GroupTest\MemberForm(Controller::curr(), 'Form');
60
        /** @var Member $member */
61
        $member = $this->objFromFixture(TestMember::class, 'admin');
62
        $form->loadDataFrom($member);
63
        $checkboxSetField = $form->Fields()->fieldByName('Groups');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $checkboxSetField is correct as $form->Fields()->fieldByName('Groups') targeting SilverStripe\Forms\FieldList::fieldByName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
64
        $checkboxSetField->setValue(
65
            [
66
            $adminGroup->ID => $adminGroup->ID, // keep existing relation
67
            $parentGroup->ID => $parentGroup->ID, // add new relation
68
            ]
69
        );
70
        $form->saveInto($member);
71
        $updatedGroups = $member->Groups();
72
73
        $this->assertEquals(
74
            2,
75
            count($updatedGroups->column()),
76
            "Adding a toplevel group works"
77
        );
78
        $this->assertContains($adminGroup->ID, $updatedGroups->column('ID'));
79
        $this->assertContains($parentGroup->ID, $updatedGroups->column('ID'));
80
81
        // Test unsetting relationship
82
        $form->loadDataFrom($member);
83
        $checkboxSetField = $form->Fields()->fieldByName('Groups');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $checkboxSetField is correct as $form->Fields()->fieldByName('Groups') targeting SilverStripe\Forms\FieldList::fieldByName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
84
        $checkboxSetField->setValue(
85
            [
86
            $adminGroup->ID => $adminGroup->ID, // keep existing relation
87
            //$parentGroup->ID => $parentGroup->ID, // remove previously set relation
88
            ]
89
        );
90
        $form->saveInto($member);
91
        $member->flushCache();
92
        $updatedGroups = $member->Groups();
93
        $this->assertEquals(
94
            1,
95
            count($updatedGroups->column()),
96
            "Removing a previously added toplevel group works"
97
        );
98
        $this->assertContains($adminGroup->ID, $updatedGroups->column('ID'));
99
    }
100
101
    public function testUnsavedGroups()
102
    {
103
        $member = $this->objFromFixture(TestMember::class, 'admin');
104
        $group = new Group();
105
        $group->Title = 'Title';
106
107
        // Can save user to unsaved group
108
        $group->Members()->add($member);
109
        $this->assertEquals([$member->ID], array_values($group->Members()->getIDList()));
110
111
        // Persists after writing to DB
112
        $group->write();
113
114
        /** @var Group $group */
115
        $group = Group::get()->byID($group->ID);
116
        $this->assertEquals([$member->ID], array_values($group->Members()->getIDList()));
117
    }
118
119
    public function testCollateAncestorIDs()
120
    {
121
        /** @var Group $parentGroup */
122
        $parentGroup = $this->objFromFixture(Group::class, 'parentgroup');
123
        /** @var Group $childGroup */
124
        $childGroup = $this->objFromFixture(Group::class, 'childgroup');
125
        $orphanGroup = new Group();
126
        $orphanGroup->Title = 'Title';
127
        $orphanGroup->ParentID = 99999;
128
        $orphanGroup->write();
129
130
        $this->assertEquals(
131
            1,
132
            count($parentGroup->collateAncestorIDs()),
133
            'Root node only contains itself'
134
        );
135
        $this->assertContains($parentGroup->ID, $parentGroup->collateAncestorIDs());
136
137
        $this->assertEquals(
138
            2,
139
            count($childGroup->collateAncestorIDs()),
140
            'Contains parent nodes, with child node first'
141
        );
142
        $this->assertContains($parentGroup->ID, $childGroup->collateAncestorIDs());
143
        $this->assertContains($childGroup->ID, $childGroup->collateAncestorIDs());
144
145
        $this->assertEquals(
146
            1,
147
            count($orphanGroup->collateAncestorIDs()),
148
            'Orphaned nodes dont contain invalid parent IDs'
149
        );
150
        $this->assertContains($orphanGroup->ID, $orphanGroup->collateAncestorIDs());
151
    }
152
153
    /**
154
     * Test that Groups including their children (recursively) are collated and returned
155
     */
156
    public function testCollateFamilyIds()
157
    {
158
        /** @var Group $group */
159
        $group = $this->objFromFixture(Group::class, 'parentgroup');
160
        $groupIds = $this->allFixtureIDs(Group::class);
161
        $ids = array_intersect_key($groupIds, array_flip(['parentgroup', 'childgroup', 'grandchildgroup']));
162
        $this->assertEquals(array_values($ids), $group->collateFamilyIDs());
163
    }
164
165
    /**
166
     * Test that an exception is thrown if collateFamilyIDs is called on an unsaved Group
167
     */
168
    public function testCannotCollateUnsavedGroupFamilyIds()
169
    {
170
        $this->expectException(\InvalidArgumentException::class);
171
        $this->expectExceptionMessage('Cannot call collateFamilyIDs on unsaved Group.');
172
        $group = new Group;
173
        $group->collateFamilyIDs();
174
    }
175
176
    /**
177
     * Test that a Group's children can be retrieved
178
     */
179
    public function testGetAllChildren()
180
    {
181
        /** @var Group $group */
182
        $group = $this->objFromFixture(Group::class, 'parentgroup');
183
        $children = $group->getAllChildren();
184
        $this->assertInstanceOf(ArrayList::class, $children);
185
        $this->assertSame(['childgroup', 'grandchildgroup'], $children->column('Code'));
186
    }
187
188
    public function testGroupInGroupMethods()
189
    {
190
        $parentGroup = $this->objFromFixture(Group::class, 'parentgroup');
191
        $childGroup = $this->objFromFixture(Group::class, 'childgroup');
192
        $grandchildGroup = $this->objFromFixture(Group::class, 'grandchildgroup');
193
        $adminGroup = $this->objFromFixture(Group::class, 'admingroup');
194
        $group1 = $this->objFromFixture(Group::class, 'group1');
195
196
        $this->assertTrue($grandchildGroup->inGroup($childGroup));
0 ignored issues
show
Bug introduced by
The method inGroup() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

196
        $this->assertTrue($grandchildGroup->/** @scrutinizer ignore-call */ inGroup($childGroup));
Loading history...
197
        $this->assertTrue($grandchildGroup->inGroup($childGroup->ID));
198
        $this->assertTrue($grandchildGroup->inGroup($childGroup->Code));
0 ignored issues
show
Bug Best Practice introduced by
The property Code does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
199
200
        $this->assertTrue($grandchildGroup->inGroup($parentGroup));
201
        $this->assertTrue($grandchildGroup->inGroups([$parentGroup, $childGroup]));
0 ignored issues
show
Bug introduced by
The method inGroups() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

201
        $this->assertTrue($grandchildGroup->/** @scrutinizer ignore-call */ inGroups([$parentGroup, $childGroup]));
Loading history...
202
        $this->assertTrue($grandchildGroup->inGroups([$childGroup, $parentGroup]));
203
        $this->assertTrue($grandchildGroup->inGroups([$parentGroup, $childGroup], true));
204
205
        $this->assertFalse($grandchildGroup->inGroup($adminGroup));
206
        $this->assertFalse($grandchildGroup->inGroups([$adminGroup, $group1]));
207
        $this->assertFalse($grandchildGroup->inGroups([$adminGroup, $childGroup], true));
208
209
        $this->assertFalse($grandchildGroup->inGroup('NotARealGroup'));
210
        $this->assertFalse($grandchildGroup->inGroup(99999999999));
211
        $this->assertFalse($grandchildGroup->inGroup(new TestMember()));
212
213
        // Edgecases
214
        $this->assertTrue($grandchildGroup->inGroup($grandchildGroup));
215
        $this->assertFalse($grandchildGroup->inGroups([]));
216
        $this->assertFalse($grandchildGroup->inGroups([], true));
217
    }
218
219
    public function testDelete()
220
    {
221
        $group = $this->objFromFixture(Group::class, 'parentgroup');
222
        $groupID = $group->ID;
223
        $childGroupID = $this->idFromFixture(Group::class, 'childgroup');
224
        $group->delete();
225
226
        $this->assertEquals(
227
            0,
228
            DataObject::get(Group::class, "\"ID\" = {$groupID}")->count(),
229
            'Group is removed'
230
        );
231
        $this->assertEquals(
232
            0,
233
            DataObject::get(Permission::class, "\"GroupID\" = {$groupID}")->count(),
234
            'Permissions removed along with the group'
235
        );
236
        $this->assertEquals(
237
            0,
238
            DataObject::get(Group::class, "\"ParentID\" = {$groupID}")->count(),
239
            'Child groups are removed'
240
        );
241
        $this->assertEquals(
242
            0,
243
            DataObject::get(Group::class, "\"ParentID\" = {$childGroupID}")->count(),
244
            'Grandchild groups are removed'
245
        );
246
    }
247
248
    public function testValidatesPrivilegeLevelOfParent()
249
    {
250
        /** @var Group $nonAdminGroup */
251
        $nonAdminGroup = $this->objFromFixture(Group::class, 'childgroup');
252
        /** @var Group $adminGroup */
253
        $adminGroup = $this->objFromFixture(Group::class, 'admingroup');
254
255
        // Making admin group parent of a non-admin group, effectively expanding is privileges
256
        $nonAdminGroup->ParentID = $adminGroup->ID;
257
258
        $this->logInWithPermission('APPLY_ROLES');
259
        $result = $nonAdminGroup->validate();
260
        $this->assertFalse(
261
            $result->isValid(),
262
            'Members with only APPLY_ROLES can\'t assign parent groups with direct ADMIN permissions'
263
        );
264
265
        $this->logInWithPermission('ADMIN');
266
        $result = $nonAdminGroup->validate();
267
        $this->assertTrue(
268
            $result->isValid(),
269
            'Members with ADMIN can assign parent groups with direct ADMIN permissions'
270
        );
271
        $nonAdminGroup->write();
272
273
        $this->logInWithPermission('ADMIN');
274
        /** @var Group $inheritedAdminGroup */
275
        $inheritedAdminGroup = $this->objFromFixture(Group::class, 'group1');
276
        $inheritedAdminGroup->ParentID = $adminGroup->ID;
277
        $inheritedAdminGroup->write(); // only works with ADMIN login
278
279
        $this->logInWithPermission('APPLY_ROLES');
280
        $result = $nonAdminGroup->validate();
281
        $this->assertFalse(
282
            $result->isValid(),
283
            'Members with only APPLY_ROLES can\'t assign parent groups with inherited ADMIN permission'
284
        );
285
    }
286
287
    public function testGroupTitleValidation()
288
    {
289
        $group1 = $this->objFromFixture(Group::class, 'group1');
290
291
        $newGroup = new Group();
292
293
        $validators = $newGroup->getCMSCompositeValidator()->getValidators();
294
        $this->assertCount(1, $validators);
295
        $this->assertInstanceOf(RequiredFields::class, $validators[0]);
296
        $this->assertTrue(in_array('Title', $validators[0]->getRequired()));
297
298
        $newGroup->Title = $group1->Title;
299
        $result = $newGroup->validate();
300
        $this->assertFalse(
301
            $result->isValid(),
302
            'Group names cannot be duplicated'
303
        );
304
305
        $newGroup->Title = 'Title';
306
        $result = $newGroup->validate();
307
        $this->assertTrue($result->isValid());
308
    }
309
310
    public function testGroupTitleDuplication()
311
    {
312
        $group = $this->objFromFixture(Group::class, 'group1');
313
        $group->Title = 'Group title modified';
314
        $group->write();
315
        $this->assertEquals('group-1', $group->Code);
0 ignored issues
show
Bug Best Practice introduced by
The property Code does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
316
317
        $group = new Group();
318
        $group->Title = 'Group 1';
319
        $group->write();
320
        $this->assertEquals('group-1-2', $group->Code);
321
322
        $group = new Group();
323
        $group->Title = 'Duplicate';
324
        $group->write();
325
        $group->Title = 'Duplicate renamed';
326
        $group->write();
327
        $this->assertEquals('duplicate', $group->Code);
328
329
        $group = new Group();
330
        $group->Title = 'Duplicate';
331
        $group->write();
332
        $group->Title = 'More renaming';
333
        $group->write();
334
        $this->assertEquals('duplicate-2', $group->Code);
335
336
        $group = new Group();
337
        $group->Title = 'Any Title';
338
        $group->Code = 'duplicate';
339
        $group->write();
340
        $this->assertEquals('duplicate-3', $group->Code);
341
342
        $group1 = new Group();
343
        $group1->Title = 'Any Title1';
344
        $group1->Code = 'some-code';
345
        $group2 = new Group();
346
        $group2->Title = 'Any Title2';
347
        $group2->Code = 'some-code';
348
        $group1->write();
349
        $group2->write();
350
        $this->assertEquals('some-code', $group1->Code);
351
        $this->assertEquals('some-code-2', $group2->Code);
352
    }
353
354
    public function testSettingCodeRepeatedly()
355
    {
356
        // Setting the code to the code it already was doesn't modify it
357
        $group = $this->objFromFixture(Group::class, 'group1');
358
        $previousCode = $group->Code;
0 ignored issues
show
Bug Best Practice introduced by
The property Code does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
359
        $group->Code = $previousCode;
0 ignored issues
show
Bug Best Practice introduced by
The property Code does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
360
        $group->write();
361
        $this->assertEquals($previousCode, $group->Code);
362
363
        // Setting the code to a new code does modify it
364
        $group->Code = 'new-code';
365
        $group->write();
366
        $this->assertEquals('new-code', $group->Code);
367
368
        // The old code can be reused
369
        $group->Code = $previousCode;
370
        $group->write();
371
        $this->assertEquals($previousCode, $group->Code);
372
    }
373
}
374