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
|
|
|
|
15
|
|
|
class InheritedPermissionsTest extends SapphireTest |
16
|
|
|
{ |
17
|
|
|
protected static $fixture_file = 'InheritedPermissionsTest.yml'; |
18
|
|
|
|
19
|
|
|
protected static $extra_dataobjects = [ |
20
|
|
|
TestPermissionNode::class, |
21
|
|
|
]; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var TestDefaultPermissionChecker |
25
|
|
|
*/ |
26
|
|
|
protected $rootPermissions = null; |
27
|
|
|
|
28
|
|
|
protected function setUp() |
29
|
|
|
{ |
30
|
|
|
parent::setUp(); |
31
|
|
|
|
32
|
|
|
// Register root permissions |
33
|
|
|
$permission = InheritedPermissions::create(TestPermissionNode::class) |
34
|
|
|
->setGlobalEditPermissions(['TEST_NODE_ACCESS']) |
35
|
|
|
->setDefaultPermissions($this->rootPermissions = new TestDefaultPermissionChecker()); |
36
|
|
|
Injector::inst()->registerService( |
37
|
|
|
$permission, |
38
|
|
|
PermissionChecker::class.'.testpermissions' |
39
|
|
|
); |
40
|
|
|
|
41
|
|
|
// Reset root permission |
42
|
|
|
} |
43
|
|
|
|
44
|
|
View Code Duplication |
public function testEditPermissions() |
|
|
|
|
45
|
|
|
{ |
46
|
|
|
$editor = $this->objFromFixture(Member::class, 'editor'); |
47
|
|
|
|
48
|
|
|
$about = $this->objFromFixture(TestPermissionNode::class, 'about'); |
49
|
|
|
$aboutStaff = $this->objFromFixture(TestPermissionNode::class, 'about-staff'); |
50
|
|
|
$history = $this->objFromFixture(TestPermissionNode::class, 'history'); |
51
|
|
|
$products = $this->objFromFixture(TestPermissionNode::class, 'products'); |
52
|
|
|
$product1 = $this->objFromFixture(TestPermissionNode::class, 'products-product1'); |
53
|
|
|
$product4 = $this->objFromFixture(TestPermissionNode::class, 'products-product4'); |
54
|
|
|
|
55
|
|
|
// Test logged out users cannot edit |
56
|
|
|
Member::actAs(null, function () use ($aboutStaff) { |
57
|
|
|
$this->assertFalse($aboutStaff->canEdit()); |
58
|
|
|
}); |
59
|
|
|
|
60
|
|
|
// Can't edit a page that is locked to admins |
61
|
|
|
$this->assertFalse($about->canEdit($editor)); |
|
|
|
|
62
|
|
|
|
63
|
|
|
// Can edit a page that is locked to editors |
64
|
|
|
$this->assertTrue($products->canEdit($editor)); |
|
|
|
|
65
|
|
|
|
66
|
|
|
// Can edit a child of that page that inherits |
67
|
|
|
$this->assertTrue($product1->canEdit($editor)); |
|
|
|
|
68
|
|
|
|
69
|
|
|
// Can't edit a child of that page that has its permissions overridden |
70
|
|
|
$this->assertFalse($product4->canEdit($editor)); |
|
|
|
|
71
|
|
|
|
72
|
|
|
// Test that root node respects root permissions |
73
|
|
|
$this->assertTrue($history->canEdit($editor)); |
|
|
|
|
74
|
|
|
|
75
|
|
|
TestPermissionNode::getInheritedPermissions()->clearCache(); |
76
|
|
|
$this->rootPermissions->setCanEdit(false); |
77
|
|
|
|
78
|
|
|
// With root edit false, permissions are now denied for CanEditType = Inherit |
79
|
|
|
$this->assertFalse($history->canEdit($editor)); |
|
|
|
|
80
|
|
|
} |
81
|
|
|
|
82
|
|
View Code Duplication |
public function testDeletePermissions() |
|
|
|
|
83
|
|
|
{ |
84
|
|
|
$editor = $this->objFromFixture(Member::class, 'editor'); |
85
|
|
|
|
86
|
|
|
$about = $this->objFromFixture(TestPermissionNode::class, 'about'); |
87
|
|
|
$aboutStaff = $this->objFromFixture(TestPermissionNode::class, 'about-staff'); |
88
|
|
|
$history = $this->objFromFixture(TestPermissionNode::class, 'history'); |
89
|
|
|
$products = $this->objFromFixture(TestPermissionNode::class, 'products'); |
90
|
|
|
$product1 = $this->objFromFixture(TestPermissionNode::class, 'products-product1'); |
91
|
|
|
$product4 = $this->objFromFixture(TestPermissionNode::class, 'products-product4'); |
92
|
|
|
|
93
|
|
|
// Test logged out users cannot edit |
94
|
|
|
Member::actAs(null, function () use ($aboutStaff) { |
95
|
|
|
$this->assertFalse($aboutStaff->canDelete()); |
96
|
|
|
}); |
97
|
|
|
|
98
|
|
|
// Can't edit a page that is locked to admins |
99
|
|
|
$this->assertFalse($about->canDelete($editor)); |
|
|
|
|
100
|
|
|
|
101
|
|
|
// Can't delete a page if a child (product4) is un-deletable |
102
|
|
|
$this->assertFalse($products->canDelete($editor)); |
|
|
|
|
103
|
|
|
|
104
|
|
|
// Can edit a child of that page that inherits |
105
|
|
|
$this->assertTrue($product1->canDelete($editor)); |
|
|
|
|
106
|
|
|
|
107
|
|
|
// Can't edit a child of that page that has its permissions overridden |
108
|
|
|
$this->assertFalse($product4->canDelete($editor)); |
|
|
|
|
109
|
|
|
|
110
|
|
|
// Test that root node respects root permissions |
111
|
|
|
$this->assertTrue($history->canDelete($editor)); |
|
|
|
|
112
|
|
|
|
113
|
|
|
TestPermissionNode::getInheritedPermissions()->clearCache(); |
114
|
|
|
$this->rootPermissions->setCanEdit(false); |
115
|
|
|
|
116
|
|
|
// With root edit false, permissions are now denied for CanEditType = Inherit |
117
|
|
|
$this->assertFalse($history->canDelete($editor)); |
|
|
|
|
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
public function testViewPermissions() |
121
|
|
|
{ |
122
|
|
|
$history = $this->objFromFixture(TestPermissionNode::class, 'history'); |
123
|
|
|
$contact = $this->objFromFixture(TestPermissionNode::class, 'contact'); |
124
|
|
|
$contactForm = $this->objFromFixture(TestPermissionNode::class, 'contact-form'); |
125
|
|
|
$secret = $this->objFromFixture(TestPermissionNode::class, 'secret'); |
126
|
|
|
$secretNested = $this->objFromFixture(TestPermissionNode::class, 'secret-nested'); |
127
|
|
|
$protected = $this->objFromFixture(TestPermissionNode::class, 'protected'); |
128
|
|
|
$protectedChild = $this->objFromFixture(TestPermissionNode::class, 'protected-child'); |
129
|
|
|
$editor = $this->objFromFixture(Member::class, 'editor'); |
130
|
|
|
|
131
|
|
|
// Not logged in user can only access Inherit or Anyone pages |
132
|
|
|
Member::actAs( |
133
|
|
|
null, |
134
|
|
|
function () use ($protectedChild, $secretNested, $protected, $secret, $history, $contact, $contactForm) { |
135
|
|
|
$this->assertTrue($history->canView()); |
136
|
|
|
$this->assertTrue($contact->canView()); |
137
|
|
|
$this->assertTrue($contactForm->canView()); |
138
|
|
|
// Protected |
139
|
|
|
$this->assertFalse($secret->canView()); |
140
|
|
|
$this->assertFalse($secretNested->canView()); |
141
|
|
|
$this->assertFalse($protected->canView()); |
142
|
|
|
$this->assertFalse($protectedChild->canView()); |
143
|
|
|
} |
144
|
|
|
); |
145
|
|
|
|
146
|
|
|
// Editor can view pages restricted to logged in users |
147
|
|
|
$this->assertTrue($secret->canView($editor)); |
|
|
|
|
148
|
|
|
$this->assertTrue($secretNested->canView($editor)); |
|
|
|
|
149
|
|
|
|
150
|
|
|
// Cannot read admin-only pages |
151
|
|
|
$this->assertFalse($protected->canView($editor)); |
|
|
|
|
152
|
|
|
$this->assertFalse($protectedChild->canView($editor)); |
|
|
|
|
153
|
|
|
|
154
|
|
|
// Check root permissions |
155
|
|
|
$this->assertTrue($history->canView($editor)); |
|
|
|
|
156
|
|
|
|
157
|
|
|
TestPermissionNode::getInheritedPermissions()->clearCache(); |
158
|
|
|
$this->rootPermissions->setCanView(false); |
159
|
|
|
|
160
|
|
|
$this->assertFalse($history->canView($editor)); |
|
|
|
|
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Test that draft permissions deny unrestricted live permissions |
165
|
|
|
*/ |
166
|
|
|
public function testRestrictedDraftUnrestrictedLive() |
167
|
|
|
{ |
168
|
|
|
Versioned::set_stage(Versioned::DRAFT); |
169
|
|
|
|
170
|
|
|
// Should be editable by non-admin editor |
171
|
|
|
/** @var TestPermissionNode $products */ |
172
|
|
|
$products = $this->objFromFixture(TestPermissionNode::class, 'products'); |
173
|
|
|
/** @var TestPermissionNode $products1 */ |
174
|
|
|
$products1 = $this->objFromFixture(TestPermissionNode::class, 'products-product1'); |
175
|
|
|
$editor = $this->objFromFixture(Member::class, 'editor'); |
176
|
|
|
|
177
|
|
|
// Ensure the editor can edit |
178
|
|
|
$this->assertTrue($products->canEdit($editor)); |
179
|
|
|
$this->assertTrue($products1->canEdit($editor)); |
180
|
|
|
|
181
|
|
|
// Write current version to live |
182
|
|
|
$products->writeToStage(Versioned::LIVE); |
183
|
|
|
$products1->writeToStage(Versioned::LIVE); |
184
|
|
|
|
185
|
|
|
// Draft version restrict to admins |
186
|
|
|
$products->EditorGroups()->setByIDList([ |
187
|
|
|
$this->idFromFixture(Group::class, 'admins') |
188
|
|
|
]); |
189
|
|
|
$products->write(); |
190
|
|
|
|
191
|
|
|
// Ensure editor can no longer edit |
192
|
|
|
TestPermissionNode::getInheritedPermissions()->clearCache(); |
193
|
|
|
$this->assertFalse($products->canEdit($editor)); |
194
|
|
|
$this->assertFalse($products1->canEdit($editor)); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Test that draft permissions permit access over live permissions |
199
|
|
|
*/ |
200
|
|
|
public function testUnrestrictedDraftOverridesLive() |
201
|
|
|
{ |
202
|
|
|
Versioned::set_stage(Versioned::DRAFT); |
203
|
|
|
|
204
|
|
|
// Should be editable by non-admin editor |
205
|
|
|
/** @var TestPermissionNode $about */ |
206
|
|
|
$about = $this->objFromFixture(TestPermissionNode::class, 'about'); |
207
|
|
|
/** @var TestPermissionNode $aboutStaff */ |
208
|
|
|
$aboutStaff = $this->objFromFixture(TestPermissionNode::class, 'about-staff'); |
209
|
|
|
$editor = $this->objFromFixture(Member::class, 'editor'); |
210
|
|
|
|
211
|
|
|
// Ensure the editor can't edit |
212
|
|
|
$this->assertFalse($about->canEdit($editor)); |
213
|
|
|
$this->assertFalse($aboutStaff->canEdit($editor)); |
214
|
|
|
|
215
|
|
|
// Write current version to live |
216
|
|
|
$about->writeToStage(Versioned::LIVE); |
217
|
|
|
$aboutStaff->writeToStage(Versioned::LIVE); |
218
|
|
|
|
219
|
|
|
// Unrestrict draft |
220
|
|
|
$about->CanEditType = InheritedPermissions::LOGGED_IN_USERS; |
|
|
|
|
221
|
|
|
$about->write(); |
222
|
|
|
|
223
|
|
|
// Ensure editor can no longer edit |
224
|
|
|
TestPermissionNode::getInheritedPermissions()->clearCache(); |
225
|
|
|
$this->assertTrue($about->canEdit($editor)); |
226
|
|
|
$this->assertTrue($aboutStaff->canEdit($editor)); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Ensure that flipping parent / child relationship on live doesn't |
231
|
|
|
* cause infinite loop |
232
|
|
|
*/ |
233
|
|
|
public function testMobiusHierarchy() |
234
|
|
|
{ |
235
|
|
|
Versioned::set_stage(Versioned::DRAFT); |
236
|
|
|
|
237
|
|
|
/** @var TestPermissionNode $history */ |
238
|
|
|
$history = $this->objFromFixture(TestPermissionNode::class, 'history'); |
239
|
|
|
/** @var TestPermissionNode $historyGallery */ |
240
|
|
|
$historyGallery = $this->objFromFixture(TestPermissionNode::class, 'history-gallery'); |
241
|
|
|
|
242
|
|
|
// Publish current state to live |
243
|
|
|
$history->writeToStage(Versioned::LIVE); |
244
|
|
|
$historyGallery->writeToStage(Versioned::LIVE); |
245
|
|
|
|
246
|
|
|
// Flip relation |
247
|
|
|
$historyGallery->ParentID = 0; |
|
|
|
|
248
|
|
|
$historyGallery->write(); |
249
|
|
|
$history->ParentID = $historyGallery->ID; |
|
|
|
|
250
|
|
|
$history->write(); |
251
|
|
|
|
252
|
|
|
// Test viewability (not logged in users) |
253
|
|
|
Member::actAs(null, function () use ($history, $historyGallery) { |
254
|
|
|
$this->assertTrue($history->canView()); |
255
|
|
|
$this->assertTrue($historyGallery->canView()); |
256
|
|
|
}); |
257
|
|
|
|
258
|
|
|
// Change permission on draft root and ensure it affects both |
259
|
|
|
$historyGallery->CanViewType = InheritedPermissions::LOGGED_IN_USERS; |
|
|
|
|
260
|
|
|
$historyGallery->write(); |
261
|
|
|
TestPermissionNode::getInheritedPermissions()->clearCache(); |
262
|
|
|
|
263
|
|
|
// Test viewability (not logged in users) |
264
|
|
|
Member::actAs(null, function () use ($history, $historyGallery) { |
265
|
|
|
$this->assertFalse($historyGallery->canView()); |
266
|
|
|
$this->assertFalse($history->canView()); |
267
|
|
|
}); |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.