Completed
Push — 4 ( 6b0b20...a8f776 )
by Damian
33s
created

testPermissionsFlushCache()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 54
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 32
nc 16
nop 0
dl 0
loc 54
rs 8.7449
c 1
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\Security\Tests;
4
5
use SilverStripe\Core\Injector\Injector;
6
use SilverStripe\Dev\SapphireTest;
7
use SilverStripe\Security\Group;
8
use SilverStripe\Security\InheritedPermissions;
9
use SilverStripe\Security\Member;
10
use SilverStripe\Security\PermissionChecker;
11
use SilverStripe\Security\Test\InheritedPermissionsTest\TestPermissionNode;
12
use SilverStripe\Security\Test\InheritedPermissionsTest\TestDefaultPermissionChecker;
13
use SilverStripe\Versioned\Versioned;
14
use Psr\SimpleCache\CacheInterface;
15
use ReflectionClass;
16
17
class InheritedPermissionsTest extends SapphireTest
18
{
19
    protected static $fixture_file = 'InheritedPermissionsTest.yml';
20
21
    protected static $extra_dataobjects = [
22
        TestPermissionNode::class,
23
    ];
24
25
    /**
26
     * @var TestDefaultPermissionChecker
27
     */
28
    protected $rootPermissions = null;
29
30
    protected function setUp()
31
    {
32
        parent::setUp();
33
34
        // Register root permissions
35
        $permission = InheritedPermissions::create(TestPermissionNode::class)
0 ignored issues
show
Bug introduced by
SilverStripe\Security\Te...stPermissionNode::class of type string is incompatible with the type array expected by parameter $args of SilverStripe\Security\In...edPermissions::create(). ( Ignorable by Annotation )

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

35
        $permission = InheritedPermissions::create(/** @scrutinizer ignore-type */ TestPermissionNode::class)
Loading history...
36
            ->setGlobalEditPermissions(['TEST_NODE_ACCESS'])
37
            ->setDefaultPermissions($this->rootPermissions = new TestDefaultPermissionChecker());
38
        Injector::inst()->registerService(
39
            $permission,
40
            PermissionChecker::class.'.testpermissions'
41
        );
42
43
        // Reset root permission
44
    }
45
46
    public function testEditPermissions()
47
    {
48
        $editor = $this->objFromFixture(Member::class, 'editor');
49
50
        $about = $this->objFromFixture(TestPermissionNode::class, 'about');
51
        $aboutStaff = $this->objFromFixture(TestPermissionNode::class, 'about-staff');
52
        $history = $this->objFromFixture(TestPermissionNode::class, 'history');
53
        $products = $this->objFromFixture(TestPermissionNode::class, 'products');
54
        $product1 = $this->objFromFixture(TestPermissionNode::class, 'products-product1');
55
        $product4 = $this->objFromFixture(TestPermissionNode::class, 'products-product4');
56
57
        // Test logged out users cannot edit
58
        Member::actAs(null, function () use ($aboutStaff) {
59
            $this->assertFalse($aboutStaff->canEdit());
60
        });
61
62
        // Can't edit a page that is locked to admins
63
        $this->assertFalse($about->canEdit($editor));
64
65
        // Can edit a page that is locked to editors
66
        $this->assertTrue($products->canEdit($editor));
67
68
        // Can edit a child of that page that inherits
69
        $this->assertTrue($product1->canEdit($editor));
70
71
        // Can't edit a child of that page that has its permissions overridden
72
        $this->assertFalse($product4->canEdit($editor));
73
74
        // Test that root node respects root permissions
75
        $this->assertTrue($history->canEdit($editor));
76
77
        TestPermissionNode::getInheritedPermissions()->clearCache();
78
        $this->rootPermissions->setCanEdit(false);
79
80
        // With root edit false, permissions are now denied for CanEditType = Inherit
81
        $this->assertFalse($history->canEdit($editor));
82
    }
83
84
    public function testDeletePermissions()
85
    {
86
        $editor = $this->objFromFixture(Member::class, 'editor');
87
88
        $about = $this->objFromFixture(TestPermissionNode::class, 'about');
89
        $aboutStaff = $this->objFromFixture(TestPermissionNode::class, 'about-staff');
90
        $history = $this->objFromFixture(TestPermissionNode::class, 'history');
91
        $products = $this->objFromFixture(TestPermissionNode::class, 'products');
92
        $product1 = $this->objFromFixture(TestPermissionNode::class, 'products-product1');
93
        $product4 = $this->objFromFixture(TestPermissionNode::class, 'products-product4');
94
95
        // Test logged out users cannot edit
96
        Member::actAs(null, function () use ($aboutStaff) {
97
            $this->assertFalse($aboutStaff->canDelete());
98
        });
99
100
        // Can't edit a page that is locked to admins
101
        $this->assertFalse($about->canDelete($editor));
102
103
        // Can't delete a page if a child (product4) is un-deletable
104
        $this->assertFalse($products->canDelete($editor));
105
106
        // Can edit a child of that page that inherits
107
        $this->assertTrue($product1->canDelete($editor));
108
109
        // Can't edit a child of that page that has its permissions overridden
110
        $this->assertFalse($product4->canDelete($editor));
111
112
        // Test that root node respects root permissions
113
        $this->assertTrue($history->canDelete($editor));
114
115
        TestPermissionNode::getInheritedPermissions()->clearCache();
116
        $this->rootPermissions->setCanEdit(false);
117
118
        // With root edit false, permissions are now denied for CanEditType = Inherit
119
        $this->assertFalse($history->canDelete($editor));
120
    }
121
122
    public function testViewPermissions()
123
    {
124
        $history = $this->objFromFixture(TestPermissionNode::class, 'history');
125
        $contact = $this->objFromFixture(TestPermissionNode::class, 'contact');
126
        $contactForm = $this->objFromFixture(TestPermissionNode::class, 'contact-form');
127
        $secret = $this->objFromFixture(TestPermissionNode::class, 'secret');
128
        $secretNested = $this->objFromFixture(TestPermissionNode::class, 'secret-nested');
129
        $protected = $this->objFromFixture(TestPermissionNode::class, 'protected');
130
        $protectedChild = $this->objFromFixture(TestPermissionNode::class, 'protected-child');
131
        $editor = $this->objFromFixture(Member::class, 'editor');
132
133
        // Not logged in user can only access Inherit or Anyone pages
134
        Member::actAs(
135
            null,
136
            function () use ($protectedChild, $secretNested, $protected, $secret, $history, $contact, $contactForm) {
137
                $this->assertTrue($history->canView());
138
                $this->assertTrue($contact->canView());
139
                $this->assertTrue($contactForm->canView());
140
                // Protected
141
                $this->assertFalse($secret->canView());
142
                $this->assertFalse($secretNested->canView());
143
                $this->assertFalse($protected->canView());
144
                $this->assertFalse($protectedChild->canView());
145
            }
146
        );
147
148
        // Editor can view pages restricted to logged in users
149
        $this->assertTrue($secret->canView($editor));
150
        $this->assertTrue($secretNested->canView($editor));
151
152
        // Cannot read admin-only pages
153
        $this->assertFalse($protected->canView($editor));
154
        $this->assertFalse($protectedChild->canView($editor));
155
156
        // Check root permissions
157
        $this->assertTrue($history->canView($editor));
158
159
        TestPermissionNode::getInheritedPermissions()->clearCache();
160
        $this->rootPermissions->setCanView(false);
161
162
        $this->assertFalse($history->canView($editor));
163
    }
164
165
    /**
166
     * Test that draft permissions deny unrestricted live permissions
167
     */
168
    public function testRestrictedDraftUnrestrictedLive()
169
    {
170
        Versioned::set_stage(Versioned::DRAFT);
171
172
        // Should be editable by non-admin editor
173
        /** @var TestPermissionNode $products */
174
        $products = $this->objFromFixture(TestPermissionNode::class, 'products');
175
        /** @var TestPermissionNode $products1 */
176
        $products1 = $this->objFromFixture(TestPermissionNode::class, 'products-product1');
177
        $editor = $this->objFromFixture(Member::class, 'editor');
178
179
        // Ensure the editor can edit
180
        $this->assertTrue($products->canEdit($editor));
181
        $this->assertTrue($products1->canEdit($editor));
182
183
        // Write current version to live
184
        $products->writeToStage(Versioned::LIVE);
0 ignored issues
show
Bug introduced by
The method writeToStage() does not exist on SilverStripe\Security\Te...Test\TestPermissionNode. 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

184
        $products->/** @scrutinizer ignore-call */ 
185
                   writeToStage(Versioned::LIVE);
Loading history...
185
        $products1->writeToStage(Versioned::LIVE);
186
187
        // Draft version restrict to admins
188
        $products->EditorGroups()->setByIDList([
0 ignored issues
show
Bug introduced by
The method EditorGroups() does not exist on SilverStripe\Security\Te...Test\TestPermissionNode. 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

188
        $products->/** @scrutinizer ignore-call */ 
189
                   EditorGroups()->setByIDList([
Loading history...
189
            $this->idFromFixture(Group::class, 'admins')
190
        ]);
191
        $products->write();
192
193
        // Ensure editor can no longer edit
194
        TestPermissionNode::getInheritedPermissions()->clearCache();
195
        $this->assertFalse($products->canEdit($editor));
196
        $this->assertFalse($products1->canEdit($editor));
197
    }
198
199
    /**
200
     * Test that draft permissions permit access over live permissions
201
     */
202
    public function testUnrestrictedDraftOverridesLive()
203
    {
204
        Versioned::set_stage(Versioned::DRAFT);
205
206
        // Should be editable by non-admin editor
207
        /** @var TestPermissionNode $about */
208
        $about = $this->objFromFixture(TestPermissionNode::class, 'about');
209
        /** @var TestPermissionNode $aboutStaff */
210
        $aboutStaff = $this->objFromFixture(TestPermissionNode::class, 'about-staff');
211
        $editor = $this->objFromFixture(Member::class, 'editor');
212
213
        // Ensure the editor can't edit
214
        $this->assertFalse($about->canEdit($editor));
215
        $this->assertFalse($aboutStaff->canEdit($editor));
216
217
        // Write current version to live
218
        $about->writeToStage(Versioned::LIVE);
219
        $aboutStaff->writeToStage(Versioned::LIVE);
220
221
        // Unrestrict draft
222
        $about->CanEditType = InheritedPermissions::LOGGED_IN_USERS;
0 ignored issues
show
Bug Best Practice introduced by
The property CanEditType does not exist on SilverStripe\Security\Te...Test\TestPermissionNode. Since you implemented __set, consider adding a @property annotation.
Loading history...
223
        $about->write();
224
225
        // Ensure editor can no longer edit
226
        TestPermissionNode::getInheritedPermissions()->clearCache();
227
        $this->assertTrue($about->canEdit($editor));
228
        $this->assertTrue($aboutStaff->canEdit($editor));
229
    }
230
231
    /**
232
     * Ensure that flipping parent / child relationship on live doesn't
233
     * cause infinite loop
234
     */
235
    public function testMobiusHierarchy()
236
    {
237
        Versioned::set_stage(Versioned::DRAFT);
238
239
        /** @var TestPermissionNode $history */
240
        $history = $this->objFromFixture(TestPermissionNode::class, 'history');
241
        /** @var TestPermissionNode $historyGallery */
242
        $historyGallery = $this->objFromFixture(TestPermissionNode::class, 'history-gallery');
243
244
        // Publish current state to live
245
        $history->writeToStage(Versioned::LIVE);
246
        $historyGallery->writeToStage(Versioned::LIVE);
247
248
        // Flip relation
249
        $historyGallery->ParentID = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property ParentID does not exist on SilverStripe\Security\Te...Test\TestPermissionNode. Since you implemented __set, consider adding a @property annotation.
Loading history...
250
        $historyGallery->write();
251
        $history->ParentID = $historyGallery->ID;
252
        $history->write();
253
254
        // Test viewability (not logged in users)
255
        Member::actAs(null, function () use ($history, $historyGallery) {
256
            $this->assertTrue($history->canView());
257
            $this->assertTrue($historyGallery->canView());
258
        });
259
260
        // Change permission on draft root and ensure it affects both
261
        $historyGallery->CanViewType = InheritedPermissions::LOGGED_IN_USERS;
0 ignored issues
show
Bug Best Practice introduced by
The property CanViewType does not exist on SilverStripe\Security\Te...Test\TestPermissionNode. Since you implemented __set, consider adding a @property annotation.
Loading history...
262
        $historyGallery->write();
263
        TestPermissionNode::getInheritedPermissions()->clearCache();
264
265
        // Test viewability (not logged in users)
266
        Member::actAs(null, function () use ($history, $historyGallery) {
267
            $this->assertFalse($historyGallery->canView());
268
            $this->assertFalse($history->canView());
269
        });
270
    }
271
272
    public function testPermissionsPersistCache()
273
    {
274
        /* @var CacheInterface $cache */
275
        $cache = Injector::inst()->create(CacheInterface::class . '.InheritedPermissions');
276
        $cache->clear();
277
278
        $member = $this->objFromFixture(Member::class, 'editor');
279
280
        /** @var TestPermissionNode $history */
281
        $history = $this->objFromFixture(TestPermissionNode::class, 'history');
282
        /** @var TestPermissionNode $historyGallery */
283
        $historyGallery = $this->objFromFixture(TestPermissionNode::class, 'history-gallery');
284
        $permissionChecker = new InheritedPermissions(TestPermissionNode::class, $cache);
285
286
        $viewKey = $this->generateCacheKey($permissionChecker, InheritedPermissions::VIEW, $member->ID);
287
        $editKey = $this->generateCacheKey($permissionChecker, InheritedPermissions::EDIT, $member->ID);
288
289
        $this->assertNull($cache->get($editKey));
290
        $this->assertNull($cache->get($viewKey));
291
292
        $permissionChecker->canEditMultiple([$history->ID, $historyGallery->ID], $member);
293
        $this->assertNull($cache->get($editKey));
294
        $this->assertNull($cache->get($viewKey));
295
296
        unset($permissionChecker);
297
        $this->assertTrue(is_array($cache->get($editKey)));
298
        $this->assertNull($cache->get($viewKey));
299
        $this->assertArrayHasKey($history->ID, $cache->get($editKey));
300
        $this->assertArrayHasKey($historyGallery->ID, $cache->get($editKey));
301
302
        $permissionChecker = new InheritedPermissions(TestPermissionNode::class, $cache);
303
        $permissionChecker->canViewMultiple([$history->ID], $member);
304
        $this->assertNotNull($cache->get($editKey));
305
        $this->assertNull($cache->get($viewKey));
306
307
        unset($permissionChecker);
308
        $this->assertTrue(is_array($cache->get($viewKey)));
309
        $this->assertTrue(is_array($cache->get($editKey)));
310
        $this->assertArrayHasKey($history->ID, $cache->get($viewKey));
311
        $this->assertArrayNotHasKey($historyGallery->ID, $cache->get($viewKey));
312
    }
313
314
    public function testPermissionsFlushCache()
315
    {
316
        /* @var CacheInterface $cache */
317
        $cache = Injector::inst()->create(CacheInterface::class . '.InheritedPermissions');
318
        $cache->clear();
319
        
320
        $permissionChecker = new InheritedPermissions(TestPermissionNode::class, $cache);
321
        $member1 = $this->objFromFixture(Member::class, 'editor');
322
        $member2 = $this->objFromFixture(Member::class, 'admin');
323
        $editKey1 = $this->generateCacheKey($permissionChecker, InheritedPermissions::EDIT, $member1->ID);
324
        $editKey2 = $this->generateCacheKey($permissionChecker, InheritedPermissions::EDIT, $member2->ID);
325
        $viewKey1 = $this->generateCacheKey($permissionChecker, InheritedPermissions::VIEW, $member1->ID);
326
        $viewKey2 = $this->generateCacheKey($permissionChecker, InheritedPermissions::VIEW, $member2->ID);
327
328
        foreach ([$editKey1, $editKey2, $viewKey1, $viewKey2] as $key) {
329
            $this->assertNull($cache->get($key));
330
        }
331
332
        /** @var TestPermissionNode $history */
333
        $history = $this->objFromFixture(TestPermissionNode::class, 'history');
334
        /** @var TestPermissionNode $historyGallery */
335
        $historyGallery = $this->objFromFixture(TestPermissionNode::class, 'history-gallery');
336
337
        $permissionChecker->canEditMultiple([$history->ID, $historyGallery->ID], $member1);
338
        $permissionChecker->canViewMultiple([$history->ID, $historyGallery->ID], $member1);
339
        $permissionChecker->canEditMultiple([$history->ID, $historyGallery->ID], $member2);
340
        $permissionChecker->canViewMultiple([$history->ID, $historyGallery->ID], $member2);
341
342
        unset($permissionChecker);
343
344
        foreach ([$editKey1, $editKey2, $viewKey1, $viewKey2] as $key) {
345
            $this->assertNotNull($cache->get($key));
346
        }
347
        $permissionChecker = new InheritedPermissions(TestPermissionNode::class, $cache);
348
349
        // Non existent ID
350
        $permissionChecker->flushMemberCache('dummy');
0 ignored issues
show
Bug introduced by
'dummy' of type string is incompatible with the type array expected by parameter $memberIDs of SilverStripe\Security\In...ons::flushMemberCache(). ( Ignorable by Annotation )

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

350
        $permissionChecker->flushMemberCache(/** @scrutinizer ignore-type */ 'dummy');
Loading history...
351
        foreach ([$editKey1, $editKey2, $viewKey1, $viewKey2] as $key) {
352
            $this->assertNotNull($cache->get($key));
353
        }
354
355
        // Precision strike
356
        $permissionChecker->flushMemberCache([$member1->ID]);
357
        // Member1 should be clear
358
        $this->assertNull($cache->get($editKey1));
359
        $this->assertNull($cache->get($viewKey1));
360
        // Member 2 is unaffected
361
        $this->assertNotNull($cache->get($editKey2));
362
        $this->assertNotNull($cache->get($viewKey2));
363
364
        // Nuclear
365
        $permissionChecker->flushMemberCache();
366
        foreach ([$editKey1, $editKey2, $viewKey1, $viewKey2] as $key) {
367
            $this->assertNull($cache->get($key));
368
        }
369
    }
370
371
    protected function generateCacheKey(InheritedPermissions $inst, $type, $memberID)
372
    {
373
        $reflection = new ReflectionClass(InheritedPermissions::class);
374
        $method = $reflection->getMethod('generateCacheKey');
375
        $method->setAccessible(true);
376
377
        return $method->invokeArgs($inst, [$type, $memberID]);
378
    }
379
}
380