Commit | Line | Data |
---|---|---|
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 |
20 | namespace api\v4\Action; |
21 | ||
22 | use Civi\Api4\Contact; | |
23 | use Civi\Api4\Email; | |
24 | ||
25 | /** | |
26 | * @group headless | |
27 | */ | |
28 | class 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 | } |