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