Merge pull request #7744 from totten/master-upg-baddir
[civicrm-core.git] / tests / phpunit / api / v3 / ACLPermissionTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * This class is intended to test ACL permission using the multisite module
30 *
31 * @package CiviCRM_APIv3
32 * @subpackage API_Contact
33 */
34 class api_v3_ACLPermissionTest extends CiviUnitTestCase {
35 protected $_apiversion = 3;
36 public $DBResetRequired = FALSE;
37 protected $_entity;
38 protected $allowedContactId = 0;
39
40 public function setUp() {
41 parent::setUp();
42 $baoObj = new CRM_Core_DAO();
43 $baoObj->createTestObject('CRM_Pledge_BAO_Pledge', array(), 1, 0);
44 $baoObj->createTestObject('CRM_Core_BAO_Phone', array(), 1, 0);
45 $config = CRM_Core_Config::singleton();
46 $config->userPermissionClass->permissions = array();
47 }
48
49 /**
50 * (non-PHPdoc)
51 * @see CiviUnitTestCase::tearDown()
52 */
53 public function tearDown() {
54 CRM_Utils_Hook::singleton()->reset();
55 $tablesToTruncate = array(
56 'civicrm_contact',
57 'civicrm_group_contact',
58 'civicrm_group',
59 'civicrm_acl',
60 'civicrm_acl_cache',
61 'civicrm_acl_entity_role',
62 'civicrm_acl_contact_cache',
63 'civicrm_contribution',
64 'civicrm_participant',
65 'civicrm_uf_match',
66 );
67 $this->quickCleanup($tablesToTruncate);
68 $config = CRM_Core_Config::singleton();
69 unset($config->userPermissionClass->permissions);
70 }
71
72 /**
73 * Function tests that an empty where hook returns no results.
74 */
75 public function testContactGetNoResultsHook() {
76 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
77 $result = $this->callAPISuccess('contact', 'get', array(
78 'check_permissions' => 1,
79 'return' => 'display_name',
80 ));
81 $this->assertEquals(0, $result['count']);
82 }
83
84 /**
85 * Function tests that an empty where hook returns exactly 1 result with "view my contact".
86 *
87 * CRM-16512 caused contacts with Edit my contact to be able to view all records.
88 */
89 public function testContactGetOneResultHookWithViewMyContact() {
90 $this->createLoggedInUser();
91 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
92 CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM', 'view my contact');
93 $result = $this->callAPISuccess('contact', 'get', array(
94 'check_permissions' => 1,
95 'return' => 'display_name',
96 ));
97 $this->assertEquals(1, $result['count']);
98 }
99
100 /**
101 * Function tests that a user with "edit my contact" can edit themselves.
102 */
103 public function testContactEditHookWithEditMyContact() {
104 $cid = $this->createLoggedInUser();
105 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
106 CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM', 'edit my contact');
107 $this->callAPISuccess('contact', 'create', array(
108 'check_permissions' => 1,
109 'id' => $cid,
110 ));
111 }
112
113 /**
114 * Ensure contact permissions extend to related entities like email
115 */
116 public function testRelatedEntityPermissions() {
117 $this->createLoggedInUser();
118 $disallowedContact = $this->individualCreate(array(), 0);
119 $this->allowedContactId = $this->individualCreate(array(), 1);
120 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereOnlyOne'));
121 CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM');
122 $testEntities = array(
123 'Email' => array('email' => 'null@nothing', 'location_type_id' => 1),
124 'Phone' => array('phone' => '123456', 'location_type_id' => 1),
125 'IM' => array('name' => 'hello', 'location_type_id' => 1),
126 'Website' => array('url' => 'http://test'),
127 'Address' => array('street_address' => '123 Sesame St.', 'location_type_id' => 1),
128 );
129 foreach ($testEntities as $entity => $params) {
130 $params += array(
131 'contact_id' => $disallowedContact,
132 'check_permissions' => 1,
133 );
134 // We should be prevented from getting or creating entities for a contact we don't have permission for
135 $this->callAPIFailure($entity, 'create', $params);
136 $results = $this->callAPISuccess($entity, 'get', array('contact_id' => $disallowedContact, 'check_permissions' => 1));
137 $this->assertEquals(0, $results['count']);
138
139 // We should be allowed to create and get for contacts we do have permission on
140 $params['contact_id'] = $this->allowedContactId;
141 $this->callAPISuccess($entity, 'create', $params);
142 $results = $this->callAPISuccess($entity, 'get', array('contact_id' => $this->allowedContactId, 'check_permissions' => 1));
143 $this->assertGreaterThan(0, $results['count']);
144 }
145 }
146
147 /**
148 * Function tests all results are returned.
149 */
150 public function testContactGetAllResultsHook() {
151 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
152 $result = $this->callAPISuccess('contact', 'get', array(
153 'check_permissions' => 1,
154 'return' => 'display_name',
155 ));
156
157 $this->assertEquals(2, $result['count']);
158 }
159
160 /**
161 * Function tests that deleted contacts are not returned.
162 */
163 public function testContactGetPermissionHookNoDeleted() {
164 $this->callAPISuccess('contact', 'create', array('id' => 2, 'is_deleted' => 1));
165 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
166 $result = $this->callAPISuccess('contact', 'get', array(
167 'check_permissions' => 1,
168 'return' => 'display_name',
169 ));
170 $this->assertEquals(1, $result['count']);
171 }
172
173 /**
174 * Test permissions limited by hook.
175 */
176 public function testContactGetHookLimitingHook() {
177 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereOnlySecond'));
178
179 $result = $this->callAPISuccess('contact', 'get', array(
180 'check_permissions' => 1,
181 'return' => 'display_name',
182 ));
183 $this->assertEquals(1, $result['count']);
184 }
185
186 /**
187 * Confirm that without check permissions we still get 2 contacts returned.
188 */
189 public function testContactGetHookLimitingHookDontCheck() {
190 $result = $this->callAPISuccess('contact', 'get', array(
191 'check_permissions' => 0,
192 'return' => 'display_name',
193 ));
194 $this->assertEquals(2, $result['count']);
195 }
196
197 /**
198 * Check that id works as a filter.
199 */
200 public function testContactGetIDFilter() {
201 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
202 $result = $this->callAPISuccess('contact', 'get', array(
203 'sequential' => 1,
204 'id' => 2,
205 'check_permissions' => 1,
206 ));
207
208 $this->assertEquals(1, $result['count']);
209 $this->assertEquals(2, $result['id']);
210 }
211
212 /**
213 * Check that address IS returned.
214 */
215 public function testContactGetAddressReturned() {
216 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereOnlySecond'));
217 $fullresult = $this->callAPISuccess('contact', 'get', array(
218 'sequential' => 1,
219 ));
220 //return doesn't work for all keys - can't fix that here so let's skip ...
221 //prefix & suffix are inconsistent due to CRM-7929
222 // unsure about others but return doesn't work on them
223 $elementsReturnDoesntSupport = array(
224 'prefix',
225 'suffix',
226 'gender',
227 'current_employer',
228 'phone_id',
229 'phone_type_id',
230 'phone',
231 'worldregion_id',
232 'world_region',
233 );
234 $expectedReturnElements = array_diff(array_keys($fullresult['values'][0]), $elementsReturnDoesntSupport);
235 $result = $this->callAPISuccess('contact', 'get', array(
236 'check_permissions' => 1,
237 'return' => $expectedReturnElements,
238 'sequential' => 1,
239 ));
240 $this->assertEquals(1, $result['count']);
241 foreach ($expectedReturnElements as $element) {
242 $this->assertArrayHasKey($element, $result['values'][0]);
243 }
244 }
245
246 /**
247 * Check that pledge IS not returned.
248 */
249 public function testContactGetPledgeIDNotReturned() {
250 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
251 $this->callAPISuccess('contact', 'get', array(
252 'sequential' => 1,
253 ));
254 $result = $this->callAPISuccess('contact', 'get', array(
255 'check_permissions' => 1,
256 'return' => 'pledge_id',
257 'sequential' => 1,
258 ));
259 $this->assertArrayNotHasKey('pledge_id', $result['values'][0]);
260 }
261
262 /**
263 * Check that pledge IS not an allowable filter.
264 */
265 public function testContactGetPledgeIDNotFiltered() {
266 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
267 $this->callAPISuccess('contact', 'get', array(
268 'sequential' => 1,
269 ));
270 $result = $this->callAPISuccess('contact', 'get', array(
271 'check_permissions' => 1,
272 'pledge_id' => 1,
273 'sequential' => 1,
274 ));
275 $this->assertEquals(2, $result['count']);
276 }
277
278 /**
279 * Check that chaining doesn't bypass permissions
280 */
281 public function testContactGetPledgeNotChainable() {
282 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereOnlySecond'));
283 $this->callAPISuccess('contact', 'get', array(
284 'sequential' => 1,
285 ));
286 $this->callAPIFailure('contact', 'get', array(
287 'check_permissions' => 1,
288 'api.pledge.get' => 1,
289 'sequential' => 1,
290 ),
291 'Error in call to pledge_get : API permission check failed for pledge/get call; missing permission: access CiviCRM.'
292 );
293 }
294
295 public function setupCoreACL() {
296 $this->createLoggedInUser();
297 $this->_permissionedDisabledGroup = $this->groupCreate(array(
298 'title' => 'pick-me-disabled',
299 'is_active' => 0,
300 'name' => 'pick-me-disabled',
301 ));
302 $this->_permissionedGroup = $this->groupCreate(array(
303 'title' => 'pick-me-active',
304 'is_active' => 1,
305 'name' => 'pick-me-active',
306 ));
307 $this->setupACL();
308 }
309
310 /**
311 * @dataProvider entities
312 * confirm that without check permissions we still get 2 contacts returned
313 * @param $entity
314 */
315 public function testEntitiesGetHookLimitingHookNoCheck($entity) {
316 CRM_Core_Config::singleton()->userPermissionClass->permissions = array();
317 $this->setUpEntities($entity);
318 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
319 $result = $this->callAPISuccess($entity, 'get', array(
320 'check_permissions' => 0,
321 'return' => 'contact_id',
322 ));
323 $this->assertEquals(2, $result['count']);
324 }
325
326 /**
327 * @dataProvider entities
328 * confirm that without check permissions we still get 2 entities returned
329 * @param $entity
330 */
331 public function testEntitiesGetCoreACLLimitingHookNoCheck($entity) {
332 $this->setupCoreACL();
333 //CRM_Core_Config::singleton()->userPermissionClass->permissions = array();
334 $this->setUpEntities($entity);
335 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
336 $result = $this->callAPISuccess($entity, 'get', array(
337 'check_permissions' => 0,
338 'return' => 'contact_id',
339 ));
340 $this->assertEquals(2, $result['count']);
341 }
342
343 /**
344 * @dataProvider entities
345 * confirm that with check permissions we don't get entities
346 * @param $entity
347 * @throws \PHPUnit_Framework_IncompleteTestError
348 */
349 public function testEntitiesGetCoreACLLimitingCheck($entity) {
350 $this->markTestIncomplete('this does not work in 4.4 but can be enabled in 4.5 or a security release of 4.4 including the important security fix CRM-14877');
351 $this->setupCoreACL();
352 $this->setUpEntities($entity);
353 $result = $this->callAPISuccess($entity, 'get', array(
354 'check_permissions' => 1,
355 'return' => 'contact_id',
356 ));
357 $this->assertEquals(0, $result['count']);
358 }
359
360 /**
361 * @dataProvider entities
362 * Function tests that an empty where hook returns no results
363 * @param string $entity
364 * @throws \PHPUnit_Framework_IncompleteTestError
365 */
366 public function testEntityGetNoResultsHook($entity) {
367 $this->markTestIncomplete('hook acls only work with contacts so far');
368 CRM_Core_Config::singleton()->userPermissionClass->permissions = array();
369 $this->setUpEntities($entity);
370 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
371 $result = $this->callAPISuccess($entity, 'get', array(
372 'check_permission' => 1,
373 ));
374 $this->assertEquals(0, $result['count']);
375 }
376
377 /**
378 * @return array
379 */
380 public static function entities() {
381 return array(array('contribution'), array('participant'));// @todo array('pledge' => 'pledge')
382 }
383
384 /**
385 * Create 2 entities
386 * @param $entity
387 */
388 public function setUpEntities($entity) {
389 $baoObj = new CRM_Core_DAO();
390 $baoObj->createTestObject(_civicrm_api3_get_BAO($entity), array(), 2, 0);
391 CRM_Core_Config::singleton()->userPermissionClass->permissions = array(
392 'access CiviCRM',
393 'access CiviContribute',
394 'access CiviEvent',
395 'view event participants',
396 );
397 }
398
399 /**
400 * No results returned.
401 *
402 * @implements CRM_Utils_Hook::aclWhereClause
403 *
404 * @param string $type
405 * @param array $tables
406 * @param array $whereTables
407 * @param int $contactID
408 * @param string $where
409 */
410 public function aclWhereHookNoResults($type, &$tables, &$whereTables, &$contactID, &$where) {
411 }
412
413 /**
414 * All results returned.
415 *
416 * @implements CRM_Utils_Hook::aclWhereClause
417 *
418 * @param string $type
419 * @param array $tables
420 * @param array $whereTables
421 * @param int $contactID
422 * @param string $where
423 */
424 public function aclWhereHookAllResults($type, &$tables, &$whereTables, &$contactID, &$where) {
425 $where = " (1) ";
426 }
427
428 /**
429 * All but first results returned.
430 * @implements CRM_Utils_Hook::aclWhereClause
431 * @param $type
432 * @param $tables
433 * @param $whereTables
434 * @param $contactID
435 * @param $where
436 */
437 public function aclWhereOnlySecond($type, &$tables, &$whereTables, &$contactID, &$where) {
438 $where = " contact_a.id > 1";
439 }
440
441 /**
442 * Only specified contact returned.
443 * @implements CRM_Utils_Hook::aclWhereClause
444 * @param $type
445 * @param $tables
446 * @param $whereTables
447 * @param $contactID
448 * @param $where
449 */
450 public function aclWhereOnlyOne($type, &$tables, &$whereTables, &$contactID, &$where) {
451 $where = " contact_a.id = " . $this->allowedContactId;
452 }
453
454 }