APIv4 - Add support for sql equations
[civicrm-core.git] / tests / phpunit / api / v4 / Action / ContactApiKeyTest.php
CommitLineData
19b53e5b
C
1<?php
2
380f3545
TO
3/*
4 +--------------------------------------------------------------------+
7d61e75f 5 | Copyright CiviCRM LLC. All rights reserved. |
380f3545 6 | |
7d61e75f
TO
7 | This work is published under the GNU AGPLv3 license with some |
8 | permitted exceptions and without any warranty. For full license |
9 | and copyright information, see https://civicrm.org/licensing |
380f3545
TO
10 +--------------------------------------------------------------------+
11 */
12
13/**
14 *
15 * @package CRM
ca5cec67 16 * @copyright CiviCRM LLC https://civicrm.org/licensing
380f3545
TO
17 */
18
19
19b53e5b
C
20namespace api\v4\Action;
21
22use Civi\Api4\Contact;
23use Civi\Api4\Email;
24
25/**
26 * @group headless
27 */
28class ContactApiKeyTest extends \api\v4\UnitTestCase {
29
30 public function testGetApiKey() {
31 \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'add contacts', 'edit api keys', 'view all contacts', 'edit all contacts'];
351af6f1
TO
32 $key = \CRM_Utils_String::createRandom(16, \CRM_Utils_String::ALPHANUMERIC);
33 $isSafe = function ($mixed) use ($key) {
351af6f1
TO
34 return strpos(json_encode($mixed), $key) === FALSE;
35 };
19b53e5b
C
36
37 $contact = Contact::create()
38 ->addValue('first_name', 'Api')
39 ->addValue('last_name', 'Key0')
40 ->addValue('api_key', $key)
41 ->addChain('email', Email::create()
42 ->addValue('contact_id', '$id')
43 ->addValue('email', 'test@key.get'),
44 0
45 )
46 ->execute()
47 ->first();
48
49 // With sufficient permission we should see the key
50 $result = Contact::get()
51 ->addWhere('id', '=', $contact['id'])
52 ->addSelect('api_key')
f4138bc4 53 ->addSelect('IF((api_key IS NULL), "yes", "no") AS is_api_key_null')
19b53e5b
C
54 ->execute()
55 ->first();
56 $this->assertEquals($key, $result['api_key']);
f4138bc4 57 $this->assertEquals('no', $result['is_api_key_null']);
351af6f1 58 $this->assertFalse($isSafe($result), "Should reveal secret details ($key): " . var_export($result, 1));
19b53e5b
C
59
60 // Can also be fetched via join
61 $email = Email::get()
84ad7693 62 ->addSelect('contact_id.api_key')
f4138bc4 63 ->addSelect('IF((contact_id.api_key IS NULL), "yes", "no") AS is_api_key_null')
19b53e5b
C
64 ->addWhere('id', '=', $contact['email']['id'])
65 ->execute()->first();
84ad7693 66 $this->assertEquals($key, $email['contact_id.api_key']);
f4138bc4 67 $this->assertEquals('no', $result['is_api_key_null']);
351af6f1 68 $this->assertFalse($isSafe($email), "Should reveal secret details ($key): " . var_export($email, 1));
19b53e5b
C
69
70 // Remove permission and we should not see the key
a689294c 71 \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'view debug output', 'view all contacts'];
19b53e5b
C
72 $result = Contact::get()
73 ->addWhere('id', '=', $contact['id'])
74 ->addSelect('api_key')
f4138bc4 75 ->addSelect('IF((api_key IS NULL), "yes", "no") AS is_api_key_null')
a689294c
CW
76 ->setDebug(TRUE)
77 ->execute();
80aabc65 78 $this->assertContains('api_key', $result->debug['unauthorized_fields']);
a689294c 79 $this->assertArrayNotHasKey('api_key', $result[0]);
f4138bc4 80 $this->assertArrayNotHasKey('is_api_key_null', $result[0]);
a689294c 81 $this->assertTrue($isSafe($result[0]), "Should NOT reveal secret details ($key): " . var_export($result[0], 1));
19b53e5b
C
82
83 // Also not available via join
84 $email = Email::get()
84ad7693 85 ->addSelect('contact_id.api_key')
f4138bc4 86 ->addSelect('IF((contact_id.api_key IS NULL), "yes", "no") AS is_api_key_null')
19b53e5b 87 ->addWhere('id', '=', $contact['email']['id'])
a689294c
CW
88 ->setDebug(TRUE)
89 ->execute();
80aabc65 90 $this->assertContains('contact_id.api_key', $email->debug['unauthorized_fields']);
84ad7693 91 $this->assertArrayNotHasKey('contact_id.api_key', $email[0]);
f4138bc4 92 $this->assertArrayNotHasKey('is_api_key_null', $result[0]);
a689294c 93 $this->assertTrue($isSafe($email[0]), "Should NOT reveal secret details ($key): " . var_export($email[0], 1));
19b53e5b
C
94
95 $result = Contact::get()
96 ->addWhere('id', '=', $contact['id'])
97 ->execute()
98 ->first();
a689294c 99 $this->assertArrayNotHasKey('api_key', $result);
351af6f1 100 $this->assertTrue($isSafe($result), "Should NOT reveal secret details ($key): " . var_export($result, 1));
19b53e5b
C
101 }
102
80aabc65
CW
103 public function testApiKeyInWhereAndOrderBy() {
104 \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'add contacts', 'edit api keys', 'view all contacts', 'edit all contacts'];
105 $keyA = 'a' . \CRM_Utils_String::createRandom(15, \CRM_Utils_String::ALPHANUMERIC);
106 $keyB = 'b' . \CRM_Utils_String::createRandom(15, \CRM_Utils_String::ALPHANUMERIC);
107
108 $firstName = uniqid('name');
109
110 $contactA = Contact::create()
111 ->addValue('first_name', $firstName)
112 ->addValue('last_name', 'KeyA')
113 ->addValue('api_key', $keyA)
114 ->execute()
115 ->first();
116
117 $contactB = Contact::create()
118 ->addValue('first_name', $firstName)
119 ->addValue('last_name', 'KeyB')
120 ->addValue('api_key', $keyB)
121 ->execute()
122 ->first();
123
124 // With sufficient permission we can ORDER BY the key
125 $result = Contact::get()
126 ->addSelect('id')
127 ->addWhere('first_name', '=', $firstName)
128 ->addOrderBy('api_key', 'DESC')
129 ->addOrderBy('id', 'ASC')
130 ->execute();
131 $this->assertEquals($contactB['id'], $result[0]['id']);
132
133 // We can also use the key in WHERE clause
134 $result = Contact::get()
135 ->addSelect('id')
136 ->addWhere('api_key', '=', $keyB)
137 ->execute();
138 $this->assertEquals($contactB['id'], $result->single()['id']);
139
140 // We can also use the key in HAVING clause
141 $result = Contact::get()
142 ->addSelect('id', 'api_key')
143 ->addHaving('api_key', '=', $keyA)
144 ->execute();
145 $this->assertEquals($contactA['id'], $result->single()['id']);
146
147 // Remove permission
148 \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'view debug output', 'view all contacts'];
149
150 // Assert we cannot ORDER BY the key
151 $result = Contact::get()
152 ->addSelect('id')
153 ->addWhere('first_name', '=', $firstName)
154 ->addOrderBy('api_key', 'DESC')
155 ->addOrderBy('id', 'ASC')
156 ->setDebug(TRUE)
157 ->execute();
158 $this->assertEquals($contactA['id'], $result[0]['id']);
159 $this->assertContains('api_key', $result->debug['unauthorized_fields']);
160
161 // Assert we cannot use the key in WHERE clause
162 $result = Contact::get()
163 ->addSelect('id')
164 ->addWhere('api_key', '=', $keyB)
165 ->setDebug(TRUE)
166 ->execute();
167 $this->assertGreaterThan(1, $result->count());
168 $this->assertContains('api_key', $result->debug['unauthorized_fields']);
169
170 // Assert we cannot use the key in HAVING clause
171 $result = Contact::get()
172 ->addSelect('id', 'api_key')
173 ->addHaving('api_key', '=', $keyA)
174 ->setDebug(TRUE)
175 ->execute();
176 $this->assertGreaterThan(1, $result->count());
177 $this->assertContains('api_key', $result->debug['unauthorized_fields']);
178
179 }
180
19b53e5b
C
181 public function testCreateWithInsufficientPermissions() {
182 \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'add contacts'];
183 $key = uniqid();
184
185 $error = '';
186 try {
187 Contact::create()
188 ->addValue('first_name', 'Api')
189 ->addValue('last_name', 'Key1')
190 ->addValue('api_key', $key)
191 ->execute()
192 ->first();
193 }
194 catch (\Exception $e) {
195 $error = $e->getMessage();
196 }
df347a8c 197 $this->assertStringContainsString('key', $error);
19b53e5b
C
198 }
199
351af6f1
TO
200 public function testGetApiKeyViaJoin() {
201 \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'view all contacts'];
202 $key = \CRM_Utils_String::createRandom(16, \CRM_Utils_String::ALPHANUMERIC);
203 $isSafe = function ($mixed) use ($key) {
204 if ($mixed instanceof Result) {
205 $mixed = $mixed->getArrayCopy();
206 }
207 return strpos(json_encode($mixed), $key) === FALSE;
208 };
209
6764a9d3 210 $contact = Contact::create(FALSE)
351af6f1
TO
211 ->addValue('first_name', 'Api')
212 ->addValue('last_name', 'Key0')
213 ->addValue('api_key', $key)
214 ->execute()
215 ->first();
216 $this->assertFalse($isSafe($contact), "Should reveal secret details ($key): " . var_export($contact, 1));
217
6764a9d3 218 Email::create(FALSE)
351af6f1
TO
219 ->addValue('email', 'foo@example.org')
220 ->addValue('contact_id', $contact['id'])
221 ->execute();
222
6764a9d3 223 $result = Email::get(FALSE)
351af6f1
TO
224 ->addWhere('contact_id', '=', $contact['id'])
225 ->addSelect('email')
84ad7693 226 ->addSelect('contact_id.api_key')
351af6f1
TO
227 ->execute()
228 ->first();
229 $this->assertFalse($isSafe($result), "Should reveal secret details ($key): " . var_export($result, 1));
230
6764a9d3 231 $result = Email::get(TRUE)
351af6f1 232 ->addWhere('contact_id', '=', $contact['id'])
84ad7693 233 ->addSelect('contact_id.api_key')
351af6f1
TO
234 ->execute()
235 ->first();
236 $this->assertTrue($isSafe($result), "Should NOT reveal secret details ($key): " . var_export($result, 1));
237 }
238
19b53e5b
C
239 public function testUpdateApiKey() {
240 \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'edit all contacts'];
241 $key = uniqid();
242
6764a9d3 243 $contact = Contact::create(FALSE)
19b53e5b
C
244 ->addValue('first_name', 'Api')
245 ->addValue('last_name', 'Key2')
246 ->addValue('api_key', $key)
247 ->execute()
248 ->first();
249
250 $error = '';
251 try {
252 // Try to update the key without permissions; nothing should happen
253 Contact::update()
254 ->addWhere('id', '=', $contact['id'])
255 ->addValue('api_key', "NotAllowed")
256 ->execute();
257 }
258 catch (\Exception $e) {
259 $error = $e->getMessage();
260 }
261
6764a9d3 262 $result = Contact::get(FALSE)
19b53e5b
C
263 ->addWhere('id', '=', $contact['id'])
264 ->addSelect('api_key')
265 ->execute()
266 ->first();
267
df347a8c 268 $this->assertStringContainsString('key', $error);
19b53e5b
C
269
270 // Assert key is still the same
271 $this->assertEquals($result['api_key'], $key);
272
273 // Now we can update the key
274 \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'administer CiviCRM', 'edit all contacts'];
275
276 Contact::update()
277 ->addWhere('id', '=', $contact['id'])
278 ->addValue('api_key', "IGotThePower!")
279 ->execute();
280
281 $result = Contact::get()
282 ->addWhere('id', '=', $contact['id'])
283 ->addSelect('api_key')
284 ->execute()
285 ->first();
286
287 // Assert key was updated
288 $this->assertEquals($result['api_key'], "IGotThePower!");
289 }
290
291 public function testUpdateOwnApiKey() {
292 \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'edit own api keys', 'edit all contacts'];
293 $key = uniqid();
294
6764a9d3 295 $contact = Contact::create(FALSE)
19b53e5b
C
296 ->addValue('first_name', 'Api')
297 ->addValue('last_name', 'Key3')
298 ->addValue('api_key', $key)
299 ->execute()
300 ->first();
301
302 $error = '';
303 try {
304 // Try to update the key without permissions; nothing should happen
305 Contact::update()
306 ->addWhere('id', '=', $contact['id'])
307 ->addValue('api_key', "NotAllowed")
308 ->execute();
309 }
310 catch (\Exception $e) {
311 $error = $e->getMessage();
312 }
313
df347a8c 314 $this->assertStringContainsString('key', $error);
19b53e5b 315
6764a9d3 316 $result = Contact::get(FALSE)
19b53e5b
C
317 ->addWhere('id', '=', $contact['id'])
318 ->addSelect('api_key')
319 ->execute()
320 ->first();
321
322 // Assert key is still the same
323 $this->assertEquals($result['api_key'], $key);
324
325 // Now we can update the key
326 \CRM_Core_Session::singleton()->set('userID', $contact['id']);
327
328 Contact::update()
329 ->addWhere('id', '=', $contact['id'])
330 ->addValue('api_key', "MyId!")
331 ->execute();
332
6764a9d3 333 $result = Contact::get(FALSE)
19b53e5b
C
334 ->addWhere('id', '=', $contact['id'])
335 ->addSelect('api_key')
336 ->execute()
337 ->first();
338
339 // Assert key was updated
340 $this->assertEquals($result['api_key'], "MyId!");
341 }
342
343 public function testApiKeyWithGetFields() {
344 // With sufficient permissions the field should exist
345 \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'edit api keys'];
346 $this->assertArrayHasKey('api_key', \civicrm_api4('Contact', 'getFields', [], 'name'));
347 \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'administer CiviCRM'];
348 $this->assertArrayHasKey('api_key', \civicrm_api4('Contact', 'getFields', [], 'name'));
349
350 // Field hidden from non-privileged users...
351 \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'edit own api keys'];
352 $this->assertArrayNotHasKey('api_key', \civicrm_api4('Contact', 'getFields', [], 'name'));
353
354 // ...unless you disable 'checkPermissions'
355 $this->assertArrayHasKey('api_key', \civicrm_api4('Contact', 'getFields', ['checkPermissions' => FALSE], 'name'));
356 }
357
358}