Issues (493)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/addressbook.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * ownCloud - Addressbook
4
 *
5
 * @author Thomas Tanghus
6
 * @copyright 2013-2014 Thomas Tanghus ([email protected])
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
10
 * License as published by the Free Software Foundation; either
11
 * version 3 of the License, or any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
17
 *
18
 * You should have received a copy of the GNU Affero General Public
19
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
20
 *
21
 */
22
23
namespace OCA\Contacts;
24
25
use OC_L10N;
26
use OCA\Contacts\Backend\AbstractBackend;
27
use OCP\AppFramework\Http;
28
29
/**
30
 * This class manages our addressbooks.
31
 */
32
33
class Addressbook extends AbstractPIMCollection {
34
35
	/**
36
	 * @brief language object
37
	 *
38
	 * @var OC_L10N
39
	 */
40
	public static $l10n;
41
42
	protected $_count;
43
44
	/**
45
	 * @var Backend\AbstractBackend
46
	 */
47
	protected $backend;
48
49
	/**
50
	 * An array containing the mandatory:
51
	 * 	'displayname'
52
	 * 	'discription'
53
	 * 	'permissions'
54
	 *
55
	 * And the optional:
56
	 * 	'Etag'
57
	 * 	'lastModified'
58
	 *
59
	 * @var array
60
	 */
61
	protected $addressBookInfo;
62
63
	/**
64
	 * @param AbstractBackend $backend The storage backend
65
	 * @param array $addressBookInfo
66
	 * @throws \Exception
67
	 */
68 18
	public function __construct(Backend\AbstractBackend $backend, array $addressBookInfo) {
69 18
		self::$l10n = \OCP\Util::getL10N('contacts');
70 18
		$this->backend = $backend;
71 18
		$this->addressBookInfo = $addressBookInfo;
72 18
		if (is_null($this->getId())) {
73 2
			$id = $this->backend->createAddressBook($addressBookInfo);
74 2
			if ($id === false) {
75
				throw new \Exception('Error creating address book.', Http::STATUS_INTERNAL_SERVER_ERROR);
76
			}
77
78 2
			$this->addressBookInfo = $this->backend->getAddressBook($id);
79 2
		}
80
81
		//\OCP\Util::writeLog('contacts', __METHOD__.' backend: ' . print_r($this->backend, true), \OCP\Util::DEBUG);
82 18
	}
83
84
	/**
85
	 * @return AbstractBackend
86
	 */
87 8
	public function getBackend() {
88 8
		return $this->backend;
89
	}
90
91
	/**
92
	 * @return string|null
93
	 */
94 18
	public function getId() {
95 18
		return isset($this->addressBookInfo['id'])
96 18
			? $this->addressBookInfo['id']
97 18
			: null;
98
	}
99
100
	/**
101
	 * @return array
102
	 */
103 8
	public function getMetaData() {
104 4
		$metadata = $this->addressBookInfo;
105 4
		$metadata['lastmodified'] = $this->lastModified();
106 4
		$metadata['active'] = $this->isActive();
107 8
		$metadata['backend'] = $this->getBackend()->name;
108 4
		$metadata['owner'] = $this->getOwner();
109 4
		return $metadata;
110
	}
111
112
	/**
113
	 * @return string
114
	 */
115 1
	public function getDisplayName() {
116 1
		return $this->addressBookInfo['displayname'];
117
	}
118
119
	/**
120
	 * @return string
121
	 */
122
	public function getURI() {
123
		return $this->addressBookInfo['uri'];
124
	}
125
126
	/**
127
	 * @return string
128
	 */
129 4
	public function getOwner() {
130 4
		return isset($this->addressBookInfo['owner'])
131 4
			? $this->addressBookInfo['owner']
132 4
			: \OC::$server->getUserSession()->getUser()->getUId();
133
	}
134
135
	/**
136
	 * Returns the lowest permission of what the backend allows and what it supports.
137
	 * @return int
138
	 */
139 12
	public function getPermissions() {
140 12
		return $this->addressBookInfo['permissions'];
141
	}
142
143
	/**
144
	 * @brief Query whether an address book is active
145
	 * @return boolean
146
	 */
147 4
	public function isActive() {
148 4
		return $this->backend->isActive($this->getId());
149
	}
150
151
	/**
152
	 * @brief Activate an address book
153
	 * @param bool $active
154
	 * @return void
155
	 */
156
	public function setActive($active) {
157
		$this->backend->setActive($active, $this->getId());
158
	}
159
160
	/**
161
	* Returns a specific child node, referenced by its id
162
	*
163
	* @param string $id
164
	* @return Contact|null
165
	* @throws \Exception On not found
166
	*/
167 6
	public function getChild($id) {
168
		//\OCP\Util::writeLog('contacts', __METHOD__.' id: '.$id, \OCP\Util::DEBUG);
169 6
		if (!$this->hasPermission(\OCP\PERMISSION_READ)) {
170
			throw new \Exception(
171
				self::$l10n->t('You do not have permissions to see this contact'),
172
				Http::STATUS_FORBIDDEN
173
			);
174
		}
175
176 6
		if (!isset($this->objects[(string)$id])) {
177 6
			$contact = $this->backend->getContact($this->getId(), $id);
178 6
			if ($contact) {
179 5
				$this->objects[(string)$id] = new Contact($this, $this->backend, $contact);
180 5
			} else {
181 3
				throw new \Exception(
182 3
					self::$l10n->t('Contact not found'),
183
					Http::STATUS_NOT_FOUND
184 3
				);
185
			}
186 5
		}
187
188
		// When requesting a single contact we preparse it
189 5
		if (isset($this->objects[(string)$id])) {
190 5
			$this->objects[(string)$id]->retrieve();
191 5
			return $this->objects[(string)$id];
192
		}
193
	}
194
195
	/**
196
	* Checks if a child-node with the specified id exists
197
	*
198
	* @param string $id
199
	* @return bool
200
	*/
201 1
	public function childExists($id) {
202 1
		if(isset($this->objects[$id])) {
203
			return true;
204
		}
205
		try {
206 1
			return ($this->getChild($id) !== null);
207 1
		} catch (\Exception $e) {
208 1
			return false;
209
		}
210
	}
211
212
	/**
213
	* Returns an array with all the child nodes
214
	*
215
	* @param int $limit
216
	* @param int $offset
217
	* @param bool $omitdata
218
	* @return Contact[]
219
	*/
220 2
	public function getChildren($limit = null, $offset = null, $omitdata = false) {
221 2
		if (!$this->hasPermission(\OCP\PERMISSION_READ)) {
222
			throw new \Exception(
223
				self::$l10n->t('You do not have permissions to see these contacts'),
224
				Http::STATUS_FORBIDDEN
225
			);
226
		}
227
228 2
		$contacts = array();
229
230 2
		$options = array('limit' => $limit, 'offset' => $offset, 'omitdata' => $omitdata);
231 2
		foreach ($this->backend->getContacts($this->getId(), $options) as $contact) {
232
			//\OCP\Util::writeLog('contacts', __METHOD__.' id: '.$contact['id'], \OCP\Util::DEBUG);
233 1
			if (!isset($this->objects[$contact['id']])) {
234 1
				$this->objects[$contact['id']] = new Contact($this, $this->backend, $contact);
235 1
			}
236
237 1
			$contacts[] = $this->objects[$contact['id']];
238 2
		}
239
240
		//\OCP\Util::writeLog('contacts', __METHOD__.' children: '.count($contacts), \OCP\Util::DEBUG);
241 2
		return $contacts;
242
	}
243
244
	/**
245
	 * Add a contact to the address book
246
	 * This takes an array or a VCard|Contact and return
247
	 * the ID or false.
248
	 *
249
	 * @param array|VObject\VCard $data
250
	 * @return int|bool
251
	 * @throws \Exception on missing permissions
252
	 */
253 3
	public function addChild($data = null) {
254 3
		if (!$this->hasPermission(\OCP\PERMISSION_CREATE)) {
255
			throw new \Exception(
256
				self::$l10n->t('You do not have permissions add contacts to the address book'),
257
				Http::STATUS_FORBIDDEN
258
			);
259
		}
260
261 3 View Code Duplication
		if (!$this->getBackend()->hasContactMethodFor(\OCP\PERMISSION_CREATE)) {
262
			throw new \Exception(
263
				self::$l10n->t('The backend for this address book does not support adding contacts'),
264
				Http::STATUS_NOT_IMPLEMENTED
265
			);
266
		}
267
268 3
		$contact = new Contact($this, $this->backend, $data);
269
270 3
		if (is_null($data)) {
271
			// A new Contact, don't try to load from backend
272
			$contact->setRetrieved(true);
273
		}
274
275 3
		if ($contact->save() === false) {
276
			return false;
277
		}
278
279 3
		$id = $contact->getId();
280
281
		// If this method is called directly the index isn't set.
282 3
		if (!isset($this->objects[$id])) {
283 3
			$this->objects[$id] = $contact;
284 3
		}
285
286
		/* If count() hasn't been called yet don't _count hasn't been initialized
287
		 * so incrementing it would give a misleading value.
288
		 */
289 3
		if (isset($this->_count)) {
290
			$this->_count += 1;
291
		}
292
293
		//\OCP\Util::writeLog('contacts', __METHOD__.' id: ' . $id, \OCP\Util::DEBUG);
294 3
		return $id;
295
	}
296
297
	/**
298
	 * Delete a contact from the address book
299
	 *
300
	 * @param string $id
301
	 * @param array $options
302
	 * @return bool
303
	 * @throws \Exception on missing permissions
304
	 */
305 2
	public function deleteChild($id, $options = array()) {
306 2
		if (!$this->hasPermission(\OCP\PERMISSION_DELETE)) {
307
			throw new \Exception(
308
				self::$l10n->t('You do not have permissions to delete this contact'),
309
				Http::STATUS_FORBIDDEN
310
			);
311
		}
312
313 2 View Code Duplication
		if (!$this->getBackend()->hasContactMethodFor(\OCP\PERMISSION_DELETE)) {
314
			throw new \Exception(
315
				self::$l10n->t('The backend for this address book does not support deleting contacts'),
316
				Http::STATUS_NOT_IMPLEMENTED
317
			);
318
		}
319
320 2
		if ($this->backend->deleteContact($this->getId(), $id, $options)) {
321 2
			if (isset($this->objects[$id])) {
322 1
				unset($this->objects[$id]);
323 1
			}
324
325
			/* If count() hasn't been called yet don't _count hasn't been initialized
326
			* so decrementing it would give a misleading value.
327
			*/
328 2
			if (isset($this->_count)) {
329
				$this->_count -= 1;
330
			}
331
332 2
			return true;
333
		}
334
335
		return false;
336
	}
337
338
	/**
339
	 * Delete a list of contacts from the address book
340
	 *
341
	 * @param array $ids
342
	 * @return array containing the status
343
	 * @throws \Exception on missing permissions
344
	 */
345
	public function deleteChildren($ids) {
346
		if (!$this->hasPermission(\OCP\PERMISSION_DELETE)) {
347
			throw new \Exception(
348
				self::$l10n->t('You do not have permissions to delete this contact'),
349
				Http::STATUS_FORBIDDEN
350
			);
351
		}
352
353 View Code Duplication
		if (!$this->getBackend()->hasContactMethodFor(\OCP\PERMISSION_DELETE)) {
354
			throw new \Exception(
355
				self::$l10n->t('The backend for this address book does not support deleting contacts'),
356
				Http::STATUS_NOT_IMPLEMENTED
357
			);
358
		}
359
360
		$response = array();
361
362
		\OCP\Util::emitHook('OCA\Contacts', 'pre_deleteContact',
363
			array('id' => $ids)
364
		);
365
366
		foreach ($ids as $id) {
367
			try {
368
				if (!$this->deleteChild($id, array('isBatch' => true))) {
369
					\OCP\Util::writeLog(
370
						'contacts', __METHOD__.' Error deleting contact: '
371
						. $this->getBackend()->name . '::'
372
						. $this->getId() . '::' . $id,
373
						\OCP\Util::ERROR
374
					);
375
					$response[] = array(
376
						'id' => (string)$id,
377
						'status' => 'error',
378
						'message' => self::$l10n->t('Unknown error')
379
					);
380
				} else {
381
					$response[] = array(
382
						'id' => (string)$id,
383
						'status' => 'success'
384
					);
385
				}
386
			} catch(\Exception $e) {
387
				$response[] = array(
388
					'id' => (string)$id,
389
					'status' => 'error',
390
					'message' => $e->getMessage()
391
				);
392
			}
393
		}
394
		return $response;
395
	}
396
397
	/**
398
	 * @internal implements Countable
399
	 * @return int|null
400
	 */
401 1
	public function count() {
402 1
		if (!isset($this->_count)) {
403 1
			$this->_count = $this->backend->numContacts($this->getId());
404 1
		}
405
406 1
		return $this->_count;
407
	}
408
409
	/**
410
	 * Update and save the address book data to backend
411
	 * NOTE: @see IPIMObject::update for consistency considerations.
412
	 *
413
	 * @param array $data
414
	 * @return bool
415
	 */
416 1
	public function update(array $data) {
417 1
		if (!$this->hasPermission(\OCP\PERMISSION_UPDATE)) {
418
			throw new \Exception(
419
				self::$l10n->t('Access denied'),
420
				Http::STATUS_FORBIDDEN
421
			);
422
		}
423
424 1 View Code Duplication
		if (!$this->getBackend()->hasContactMethodFor(\OCP\PERMISSION_UPDATE)) {
425
			throw new \Exception(
426
				self::$l10n->t('The backend for this address book does not support updating'),
427
				Http::STATUS_NOT_IMPLEMENTED
428
			);
429
		}
430
431 1
		if (count($data) === 0) {
432
			return false;
433
		}
434
435 1
		foreach ($data as $key => $value) {
436
			switch ($key) {
437 1
				case 'displayname':
438 1
					$this->addressBookInfo['displayname'] = $value;
439 1
					break;
440
				case 'description':
441
					$this->addressBookInfo['description'] = $value;
442
					break;
443
			}
444 1
		}
445
446 1
		return $this->backend->updateAddressBook($this->getId(), $data);
447
	}
448
449
	/**
450
	 * Delete the address book from backend
451
	 *
452
	 * @return bool
453
	 */
454 1
	public function delete() {
455 1
		if (!$this->hasPermission(\OCP\PERMISSION_DELETE)) {
456
			throw new \Exception(
457
				self::$l10n->t('You don\'t have permissions to delete the address book.'),
458
				Http::STATUS_FORBIDDEN
459
			);
460
		}
461
462 1
		return $this->backend->deleteAddressBook($this->getId());
463
	}
464
465
	/**
466
	 * @brief Get the last modification time for the object.
467
	 *
468
	 * Must return a UNIX time stamp or null if the backend
469
	 * doesn't support it.
470
	 *
471
	 * @return int | null
472
	 */
473 5
	public function lastModified() {
474 5
		return $this->backend->lastModifiedAddressBook($this->getId());
475
	}
476
477
	/**
478
	 * Get an array of birthday events for contacts in this address book.
479
	 *
480
	 * @return \Sabre\VObject\Component\VEvent[]
481
	 */
482
	public function getBirthdayEvents() {
483
484
		$events = array();
485
486
		foreach ($this->getChildren() as $contact) {
487
			if ($event = $contact->getBirthdayEvent()) {
0 ignored issues
show
Are you sure the assignment to $event is correct as $contact->getBirthdayEvent() (which targets OCA\Contacts\Contact::getBirthdayEvent()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
488
				$events[] = $event;
489
			}
490
		}
491
492
		return $events;
493
	}
494
495
	/**
496
	 * Returns the searchProvider for a specific backend.
497
	 *
498
	 * @return \OCP\IAddressBook
499
	 */
500
	public function getSearchProvider() {
501
		return $this->backend->getSearchProvider($this);
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class OCA\Contacts\Backend\AbstractBackend as the method getSearchProvider() does only exist in the following sub-classes of OCA\Contacts\Backend\AbstractBackend: OCA\Contacts\Backend\Database, OCA\Contacts\Backend\Ldap, OCA\Contacts\Backend\Shared. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
502
	}
503
}
504