Passed
Pull Request — 4 (#10113)
by
unknown
07:47 queued 33s
created

GroupTest::testValidatesPrivilegeLevelOfParent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 23
nc 1
nop 0
dl 0
loc 36
rs 9.552
c 0
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\ORM\ArrayList;
9
use SilverStripe\ORM\DataObject;
10
use SilverStripe\Security\Group;
11
use SilverStripe\Security\Member;
12
use SilverStripe\Security\Permission;
13
use SilverStripe\Security\Tests\GroupTest\TestMember;
14
15
class GroupTest extends FunctionalTest
16
{
17
    protected static $fixture_file = 'GroupTest.yml';
18
19
    protected static $extra_dataobjects = [
20
        TestMember::class
21
    ];
22
23
    protected function setUp()
24
    {
25
        parent::setUp();
26
    }
27
28
    public function testGroupCodeDefaultsToTitle()
29
    {
30
        $g1 = new Group();
31
        $g1->Title = "My Title";
32
        $g1->write();
33
        $this->assertEquals('my-title', $g1->Code, 'Custom title gets converted to code if none exists already');
34
35
        $g2 = new Group();
36
        $g2->Title = "My Title";
37
        $g2->Code = "my-code";
38
        $g2->write();
39
        $this->assertEquals('my-code', $g2->Code, 'Custom attributes are not overwritten by Title field');
40
41
        $g3 = new Group();
42
        $g3->Title = _t('SilverStripe\\Admin\\SecurityAdmin.NEWGROUP', "New Group");
43
        $g3->write();
44
        $this->assertNull($g3->Code, 'Default title doesnt trigger attribute setting');
45
    }
46
47
    /**
48
     * @skipUpgrade
49
     */
50
    public function testMemberGroupRelationForm()
51
    {
52
        $this->logInAs($this->idFromFixture(TestMember::class, 'admin'));
53
54
        $adminGroup = $this->objFromFixture(Group::class, 'admingroup');
55
        $parentGroup = $this->objFromFixture(Group::class, 'parentgroup');
56
57
        // Test single group relation through checkboxsetfield
58
        $form = new GroupTest\MemberForm(Controller::curr(), 'Form');
59
        /** @var Member $member */
60
        $member = $this->objFromFixture(TestMember::class, 'admin');
61
        $form->loadDataFrom($member);
62
        $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...
63
        $checkboxSetField->setValue(
64
            [
65
            $adminGroup->ID => $adminGroup->ID, // keep existing relation
66
            $parentGroup->ID => $parentGroup->ID, // add new relation
67
            ]
68
        );
69
        $form->saveInto($member);
70
        $updatedGroups = $member->Groups();
71
72
        $this->assertEquals(
73
            2,
74
            count($updatedGroups->column()),
75
            "Adding a toplevel group works"
76
        );
77
        $this->assertContains($adminGroup->ID, $updatedGroups->column('ID'));
78
        $this->assertContains($parentGroup->ID, $updatedGroups->column('ID'));
79
80
        // Test unsetting relationship
81
        $form->loadDataFrom($member);
82
        $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...
83
        $checkboxSetField->setValue(
84
            [
85
            $adminGroup->ID => $adminGroup->ID, // keep existing relation
86
            //$parentGroup->ID => $parentGroup->ID, // remove previously set relation
87
            ]
88
        );
89
        $form->saveInto($member);
90
        $member->flushCache();
91
        $updatedGroups = $member->Groups();
92
        $this->assertEquals(
93
            1,
94
            count($updatedGroups->column()),
95
            "Removing a previously added toplevel group works"
96
        );
97
        $this->assertContains($adminGroup->ID, $updatedGroups->column('ID'));
98
    }
99
100
    public function testUnsavedGroups()
101
    {
102
        $member = $this->objFromFixture(TestMember::class, 'admin');
103
        $group = new Group();
104
        $group->Title = 'Title';
105
106
        // Can save user to unsaved group
107
        $group->Members()->add($member);
108
        $this->assertEquals([$member->ID], array_values($group->Members()->getIDList()));
109
110
        // Persists after writing to DB
111
        $group->write();
112
113
        /** @var Group $group */
114
        $group = Group::get()->byID($group->ID);
115
        $this->assertEquals([$member->ID], array_values($group->Members()->getIDList()));
116
    }
117
118
    public function testCollateAncestorIDs()
119
    {
120
        /** @var Group $parentGroup */
121
        $parentGroup = $this->objFromFixture(Group::class, 'parentgroup');
122
        /** @var Group $childGroup */
123
        $childGroup = $this->objFromFixture(Group::class, 'childgroup');
124
        $orphanGroup = new Group();
125
        $orphanGroup->Title = 'Title';
126
        $orphanGroup->ParentID = 99999;
127
        $orphanGroup->write();
128
129
        $this->assertEquals(
130
            1,
131
            count($parentGroup->collateAncestorIDs()),
132
            'Root node only contains itself'
133
        );
134
        $this->assertContains($parentGroup->ID, $parentGroup->collateAncestorIDs());
135
136
        $this->assertEquals(
137
            2,
138
            count($childGroup->collateAncestorIDs()),
139
            'Contains parent nodes, with child node first'
140
        );
141
        $this->assertContains($parentGroup->ID, $childGroup->collateAncestorIDs());
142
        $this->assertContains($childGroup->ID, $childGroup->collateAncestorIDs());
143
144
        $this->assertEquals(
145
            1,
146
            count($orphanGroup->collateAncestorIDs()),
147
            'Orphaned nodes dont contain invalid parent IDs'
148
        );
149
        $this->assertContains($orphanGroup->ID, $orphanGroup->collateAncestorIDs());
150
    }
151
152
    /**
153
     * Test that Groups including their children (recursively) are collated and returned
154
     */
155
    public function testCollateFamilyIds()
156
    {
157
        /** @var Group $group */
158
        $group = $this->objFromFixture(Group::class, 'parentgroup');
159
        $groupIds = $this->allFixtureIDs(Group::class);
160
        $ids = array_intersect_key($groupIds, array_flip(['parentgroup', 'childgroup', 'grandchildgroup']));
161
        $this->assertEquals(array_values($ids), $group->collateFamilyIDs());
162
    }
163
164
    /**
165
     * Test that an exception is thrown if collateFamilyIDs is called on an unsaved Group
166
     */
167
    public function testCannotCollateUnsavedGroupFamilyIds()
168
    {
169
        $this->expectException(InvalidArgumentException::class);
170
        $this->expectExceptionMessage('Cannot call collateFamilyIDs on unsaved Group.');
171
        $group = new Group;
172
        $group->collateFamilyIDs();
173
    }
174
175
    /**
176
     * Test that a Group's children can be retrieved
177
     */
178
    public function testGetAllChildren()
179
    {
180
        /** @var Group $group */
181
        $group = $this->objFromFixture(Group::class, 'parentgroup');
182
        $children = $group->getAllChildren();
183
        $this->assertInstanceOf(ArrayList::class, $children);
184
        $this->assertSame(['childgroup', 'grandchildgroup'], $children->column('Code'));
185
    }
186
187
    public function testGroupInGroupMethods()
188
    {
189
        $parentGroup = $this->objFromFixture(Group::class, 'parentgroup');
190
        $childGroup = $this->objFromFixture(Group::class, 'childgroup');
191
        $grandchildGroup = $this->objFromFixture(Group::class, 'grandchildgroup');
192
        $adminGroup = $this->objFromFixture(Group::class, 'admingroup');
193
        $group1 = $this->objFromFixture(Group::class, 'group1');
194
195
        $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

195
        $this->assertTrue($grandchildGroup->/** @scrutinizer ignore-call */ inGroup($childGroup));
Loading history...
196
        $this->assertTrue($grandchildGroup->inGroup($childGroup->ID));
197
        $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...
198
199
        $this->assertTrue($grandchildGroup->inGroup($parentGroup));
200
        $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

200
        $this->assertTrue($grandchildGroup->/** @scrutinizer ignore-call */ inGroups([$parentGroup, $childGroup]));
Loading history...
201
        $this->assertTrue($grandchildGroup->inGroups([$childGroup, $parentGroup]));
202
        $this->assertTrue($grandchildGroup->inGroups([$parentGroup, $childGroup], true));
203
204
        $this->assertFalse($grandchildGroup->inGroup($adminGroup));
205
        $this->assertFalse($grandchildGroup->inGroups([$adminGroup, $group1]));
206
        $this->assertFalse($grandchildGroup->inGroups([$adminGroup, $childGroup], true));
207
208
        $this->assertFalse($grandchildGroup->inGroup('NotARealGroup'));
209
        $this->assertFalse($grandchildGroup->inGroup(99999999999));
210
        $this->assertFalse($grandchildGroup->inGroup(new TestMember()));
211
212
        // Edgecases
213
        $this->assertTrue($grandchildGroup->inGroup($grandchildGroup));
214
        $this->assertFalse($grandchildGroup->inGroups([]));
215
        $this->assertFalse($grandchildGroup->inGroups([], true));
216
    }
217
218
    public function testDelete()
219
    {
220
        $group = $this->objFromFixture(Group::class, 'parentgroup');
221
        $groupID = $group->ID;
222
        $childGroupID = $this->idFromFixture(Group::class, 'childgroup');
223
        $group->delete();
224
225
        $this->assertEquals(
226
            0,
227
            DataObject::get(Group::class, "\"ID\" = {$groupID}")->count(),
228
            'Group is removed'
229
        );
230
        $this->assertEquals(
231
            0,
232
            DataObject::get(Permission::class, "\"GroupID\" = {$groupID}")->count(),
233
            'Permissions removed along with the group'
234
        );
235
        $this->assertEquals(
236
            0,
237
            DataObject::get(Group::class, "\"ParentID\" = {$groupID}")->count(),
238
            'Child groups are removed'
239
        );
240
        $this->assertEquals(
241
            0,
242
            DataObject::get(Group::class, "\"ParentID\" = {$childGroupID}")->count(),
243
            'Grandchild groups are removed'
244
        );
245
    }
246
247
    public function testValidatesPrivilegeLevelOfParent()
248
    {
249
        /** @var Group $nonAdminGroup */
250
        $nonAdminGroup = $this->objFromFixture(Group::class, 'childgroup');
251
        /** @var Group $adminGroup */
252
        $adminGroup = $this->objFromFixture(Group::class, 'admingroup');
253
254
        // Making admin group parent of a non-admin group, effectively expanding is privileges
255
        $nonAdminGroup->ParentID = $adminGroup->ID;
256
257
        $this->logInWithPermission('APPLY_ROLES');
258
        $result = $nonAdminGroup->validate();
259
        $this->assertFalse(
260
            $result->isValid(),
261
            'Members with only APPLY_ROLES can\'t assign parent groups with direct ADMIN permissions'
262
        );
263
264
        $this->logInWithPermission('ADMIN');
265
        $result = $nonAdminGroup->validate();
266
        $this->assertTrue(
267
            $result->isValid(),
268
            'Members with ADMIN can assign parent groups with direct ADMIN permissions'
269
        );
270
        $nonAdminGroup->write();
271
272
        $this->logInWithPermission('ADMIN');
273
        /** @var Group $inheritedAdminGroup */
274
        $inheritedAdminGroup = $this->objFromFixture(Group::class, 'group1');
275
        $inheritedAdminGroup->ParentID = $adminGroup->ID;
276
        $inheritedAdminGroup->write(); // only works with ADMIN login
277
278
        $this->logInWithPermission('APPLY_ROLES');
279
        $result = $nonAdminGroup->validate();
280
        $this->assertFalse(
281
            $result->isValid(),
282
            'Members with only APPLY_ROLES can\'t assign parent groups with inherited ADMIN permission'
283
        );
284
    }
285
286
    public function testGroupTitleValidation()
287
    {
288
        $g1 = new Group();
289
        $g1->Title = '';
290
        $result = $g1->validate();
291
        $this->assertFalse(
292
            $result->isValid(),
293
            'Group name cannot be empty'
294
        );
295
296
        $g1->Title = null;
297
        $result = $g1->validate();
298
        $this->assertFalse(
299
            $result->isValid(),
300
            'Group name cannot be null'
301
        );
302
303
        $g1->Title = '   ';
304
        $result = $g1->validate();
305
        $this->assertFalse(
306
            $result->isValid(),
307
            'Group name cannot be only white space'
308
        );
309
310
        $g1->Title = 'Title';
311
        $result = $g1->validate();
312
        $this->assertTrue($result->isValid());
313
    }
314
}
315