Completed
Pull Request — master (#6444)
by Robbie
15:26 queued 06:39
created

GroupTest::testGetAllChildren()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 0
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Security\Tests;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\ORM\ArrayList;
7
use SilverStripe\ORM\DataObject;
8
use SilverStripe\Security\Group;
9
use SilverStripe\Dev\FunctionalTest;
10
use SilverStripe\Control\Session;
11
use SilverStripe\Security\Permission;
12
use SilverStripe\Security\Tests\GroupTest\TestMember;
13
14
class GroupTest extends FunctionalTest
15
{
16
17
    protected static $fixture_file = 'GroupTest.yml';
18
19
    protected $extraDataObjects = [
20
        TestMember::class
21
    ];
22
23
    public function testGroupCodeDefaultsToTitle()
24
    {
25
        $g1 = new Group();
26
        $g1->Title = "My Title";
27
        $g1->write();
28
        $this->assertEquals('my-title', $g1->Code, 'Custom title gets converted to code if none exists already');
29
30
        $g2 = new Group();
31
        $g2->Title = "My Title";
32
        $g2->Code = "my-code";
33
        $g2->write();
34
        $this->assertEquals('my-code', $g2->Code, 'Custom attributes are not overwritten by Title field');
35
36
        $g3 = new Group();
37
        $g3->Title = _t('SecurityAdmin.NEWGROUP', "New Group");
38
        $g3->write();
39
        $this->assertNull($g3->Code, 'Default title doesnt trigger attribute setting');
40
    }
41
42
    /**
43
     * @skipUpgrade
44
     */
45
    public function testMemberGroupRelationForm()
46
    {
47
        Session::set('loggedInAs', $this->idFromFixture(TestMember::class, 'admin'));
48
49
        $adminGroup = $this->objFromFixture(Group::class, 'admingroup');
50
        $parentGroup = $this->objFromFixture(Group::class, 'parentgroup');
51
52
        // Test single group relation through checkboxsetfield
53
        $form = new GroupTest\MemberForm(new Controller(), 'Form');
54
        $member = $this->objFromFixture(TestMember::class, 'admin');
55
        $form->loadDataFrom($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by $this->objFromFixture(\S...Member::class, 'admin') on line 54 can be null; however, SilverStripe\Forms\Form::loadDataFrom() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
56
        $checkboxSetField = $form->Fields()->fieldByName('Groups');
57
        $checkboxSetField->setValue(
58
            array(
59
            $adminGroup->ID => $adminGroup->ID, // keep existing relation
60
            $parentGroup->ID => $parentGroup->ID, // add new relation
61
            )
62
        );
63
        $form->saveInto($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by $this->objFromFixture(\S...Member::class, 'admin') on line 54 can be null; however, SilverStripe\Forms\Form::saveInto() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
64
        $updatedGroups = $member->Groups();
65
66
        $this->assertEquals(
67
            2,
68
            count($updatedGroups->column()),
69
            "Adding a toplevel group works"
70
        );
71
        $this->assertContains($adminGroup->ID, $updatedGroups->column('ID'));
72
        $this->assertContains($parentGroup->ID, $updatedGroups->column('ID'));
73
74
        // Test unsetting relationship
75
        $form->loadDataFrom($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by $this->objFromFixture(\S...Member::class, 'admin') on line 54 can be null; however, SilverStripe\Forms\Form::loadDataFrom() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
76
        $checkboxSetField = $form->Fields()->fieldByName('Groups');
77
        $checkboxSetField->setValue(
78
            array(
79
            $adminGroup->ID => $adminGroup->ID, // keep existing relation
80
            //$parentGroup->ID => $parentGroup->ID, // remove previously set relation
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
81
            )
82
        );
83
        $form->saveInto($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by $this->objFromFixture(\S...Member::class, 'admin') on line 54 can be null; however, SilverStripe\Forms\Form::saveInto() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
84
        $member->flushCache();
85
        $updatedGroups = $member->Groups();
86
        $this->assertEquals(
87
            1,
88
            count($updatedGroups->column()),
89
            "Removing a previously added toplevel group works"
90
        );
91
        $this->assertContains($adminGroup->ID, $updatedGroups->column('ID'));
92
    }
93
94
    public function testUnsavedGroups()
95
    {
96
        $member = $this->objFromFixture(TestMember::class, 'admin');
97
        $group = new Group();
98
99
        // Can save user to unsaved group
100
        $group->Members()->add($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by $this->objFromFixture(\S...Member::class, 'admin') on line 96 can be null; however, SilverStripe\ORM\ManyManyList::add() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
101
        $this->assertEquals(array($member->ID), array_values($group->Members()->getIDList()));
102
103
        // Persists after writing to DB
104
        $group->write();
105
        $group = Group::get()->byID($group->ID);
106
        $this->assertEquals(array($member->ID), array_values($group->Members()->getIDList()));
107
    }
108
109
    public function testCollateAncestorIDs()
110
    {
111
        $parentGroup = $this->objFromFixture(Group::class, 'parentgroup');
112
        $childGroup = $this->objFromFixture(Group::class, 'childgroup');
113
        $orphanGroup = new Group();
114
        $orphanGroup->ParentID = 99999;
115
        $orphanGroup->write();
116
117
        $this->assertEquals(
118
            1,
119
            count($parentGroup->collateAncestorIDs()),
120
            'Root node only contains itself'
121
        );
122
        $this->assertContains($parentGroup->ID, $parentGroup->collateAncestorIDs());
123
124
        $this->assertEquals(
125
            2,
126
            count($childGroup->collateAncestorIDs()),
127
            'Contains parent nodes, with child node first'
128
        );
129
        $this->assertContains($parentGroup->ID, $childGroup->collateAncestorIDs());
130
        $this->assertContains($childGroup->ID, $childGroup->collateAncestorIDs());
131
132
        $this->assertEquals(
133
            1,
134
            count($orphanGroup->collateAncestorIDs()),
135
            'Orphaned nodes dont contain invalid parent IDs'
136
        );
137
        $this->assertContains($orphanGroup->ID, $orphanGroup->collateAncestorIDs());
138
    }
139
140
    /**
141
     * Test that Groups including their children (recursively) are collated and returned
142
     */
143
    public function testCollateFamilyIds()
144
    {
145
        $group = $this->objFromFixture(Group::class, 'parentgroup');
146
        $groupIds = $this->allFixtureIDs(Group::class);
147
        $ids = array_intersect_key($groupIds, array_flip(['parentgroup', 'childgroup', 'grandchildgroup']));
148
        $this->assertEquals(array_values($ids), $group->collateFamilyIDs());
149
    }
150
151
    /**
152
     * Test that an exception is thrown if collateFamilyIDs is called on an unsaved Group
153
     * @expectedException InvalidArgumentException
154
     * @expectedExceptionMessage Cannot call collateFamilyIDs on unsaved Group.
155
     */
156
    public function testCannotCollateUnsavedGroupFamilyIds()
157
    {
158
        $group = new Group;
159
        $group->collateFamilyIDs();
160
    }
161
162
    /**
163
     * Test that a Group's children can be retrieved
164
     */
165
    public function testGetAllChildren()
166
    {
167
        $group = $this->objFromFixture(Group::class, 'parentgroup');
168
169
        $children = $group->getAllChildren();
170
        $this->assertInstanceOf(ArrayList::class, $children);
171
        $this->assertSame(['childgroup', 'grandchildgroup'], $children->column('Code'));
172
    }
173
174
    public function testDelete()
175
    {
176
        $group = $this->objFromFixture(Group::class, 'parentgroup');
177
        $groupID = $group->ID;
178
        $childGroupID = $this->idFromFixture(Group::class, 'childgroup');
179
        $group->delete();
180
181
        $this->assertEquals(
182
            0,
183
            DataObject::get(Group::class, "\"ID\" = {$groupID}")->count(),
184
            'Group is removed'
185
        );
186
        $this->assertEquals(
187
            0,
188
            DataObject::get(Permission::class, "\"GroupID\" = {$groupID}")->count(),
189
            'Permissions removed along with the group'
190
        );
191
        $this->assertEquals(
192
            0,
193
            DataObject::get(Group::class, "\"ParentID\" = {$groupID}")->count(),
194
            'Child groups are removed'
195
        );
196
        $this->assertEquals(
197
            0,
198
            DataObject::get(Group::class, "\"ParentID\" = {$childGroupID}")->count(),
199
            'Grandchild groups are removed'
200
        );
201
    }
202
203
    public function testValidatesPrivilegeLevelOfParent()
204
    {
205
        $nonAdminGroup = $this->objFromFixture(Group::class, 'childgroup');
206
        $adminGroup = $this->objFromFixture(Group::class, 'admingroup');
207
208
        // Making admin group parent of a non-admin group, effectively expanding is privileges
209
        $nonAdminGroup->ParentID = $adminGroup->ID;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
210
211
        $this->logInWithPermission('APPLY_ROLES');
212
        $result = $nonAdminGroup->validate();
213
        $this->assertFalse(
214
            $result->isValid(),
215
            'Members with only APPLY_ROLES can\'t assign parent groups with direct ADMIN permissions'
216
        );
217
218
        $this->logInWithPermission('ADMIN');
219
        $result = $nonAdminGroup->validate();
220
        $this->assertTrue(
221
            $result->isValid(),
222
            'Members with ADMIN can assign parent groups with direct ADMIN permissions'
223
        );
224
        $nonAdminGroup->write();
225
226
        $this->logInWithPermission('ADMIN');
227
        $inheritedAdminGroup = $this->objFromFixture(Group::class, 'group1');
228
        $inheritedAdminGroup->ParentID = $adminGroup->ID;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
229
        $inheritedAdminGroup->write(); // only works with ADMIN login
230
231
        $this->logInWithPermission('APPLY_ROLES');
232
        $result = $nonAdminGroup->validate();
233
        $this->assertFalse(
234
            $result->isValid(),
235
            'Members with only APPLY_ROLES can\'t assign parent groups with inherited ADMIN permission'
236
        );
237
    }
238
}
239