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