access.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. <?php
  2. /**
  3. * Functions for Elgg's access system for entities, metadata, and annotations.
  4. *
  5. * Access is generally saved in the database as access_id. This corresponds to
  6. * one of the ACCESS_* constants defined in {@link elgglib.php} or the ID of an
  7. * access collection.
  8. *
  9. * @package Elgg.Core
  10. * @subpackage Access
  11. */
  12. /**
  13. * Set if Elgg's access system should be ignored.
  14. *
  15. * The access system will not return entities in any getter functions if the
  16. * user doesn't have access. This removes this restriction.
  17. *
  18. * When the access system is being ignored, all checks for create, retrieve,
  19. * update, and delete should pass. This affects all the canEdit() and related
  20. * methods.
  21. *
  22. * @tip Use this to access entities in automated scripts
  23. * when no user is logged in.
  24. *
  25. * @warning This will not show disabled entities.
  26. * Use {@link access_show_hidden_entities()} to access disabled entities.
  27. *
  28. * @note Internal: The access override is checked in elgg_override_permissions(). It is
  29. * registered for the 'permissions_check' hooks to override the access system for
  30. * the canEdit() and canWriteToContainer() methods.
  31. *
  32. * @note Internal: This clears the access cache.
  33. *
  34. * @note Internal: For performance reasons this is done at the database access clause level.
  35. *
  36. * @param bool $ignore If true, disables all access checks.
  37. *
  38. * @return bool Previous ignore_access setting.
  39. * @since 1.7.0
  40. * @see elgg_get_ignore_access()
  41. */
  42. function elgg_set_ignore_access($ignore = true) {
  43. return _elgg_services()->session->setIgnoreAccess($ignore);
  44. }
  45. /**
  46. * Get current ignore access setting.
  47. *
  48. * @return bool
  49. * @since 1.7.0
  50. * @see elgg_set_ignore_access()
  51. */
  52. function elgg_get_ignore_access() {
  53. return _elgg_services()->session->getIgnoreAccess();
  54. }
  55. /**
  56. * Return a string of access_ids for $user_guid appropriate for inserting into an SQL IN clause.
  57. *
  58. * @uses get_access_array
  59. *
  60. * @see get_access_array()
  61. *
  62. * @param int $user_guid User ID; defaults to currently logged in user
  63. * @param int $site_guid Site ID; defaults to current site
  64. * @param bool $flush If set to true, will refresh the access list from the
  65. * database rather than using this function's cache.
  66. *
  67. * @return string A list of access collections suitable for using in an SQL call
  68. * @access private
  69. */
  70. function get_access_list($user_guid = 0, $site_guid = 0, $flush = false) {
  71. return _elgg_services()->accessCollections->getAccessList($user_guid, $site_guid, $flush);
  72. }
  73. /**
  74. * Returns an array of access IDs a user is permitted to see.
  75. *
  76. * Can be overridden with the 'access:collections:read', 'user' plugin hook.
  77. * @warning A callback for that plugin hook needs to either not retrieve data
  78. * from the database that would use the access system (triggering the plugin again)
  79. * or ignore the second call. Otherwise, an infinite loop will be created.
  80. *
  81. * This returns a list of all the collection ids a user owns or belongs
  82. * to plus public and logged in access levels. If the user is an admin, it includes
  83. * the private access level.
  84. *
  85. * @note Internal: this is only used in core for creating the SQL where clause when
  86. * retrieving content from the database. The friends access level is handled by
  87. * _elgg_get_access_where_sql().
  88. *
  89. * @see get_write_access_array() for the access levels that a user can write to.
  90. *
  91. * @param int $user_guid User ID; defaults to currently logged in user
  92. * @param int $site_guid Site ID; defaults to current site
  93. * @param bool $flush If set to true, will refresh the access ids from the
  94. * database rather than using this function's cache.
  95. *
  96. * @return array An array of access collections ids
  97. */
  98. function get_access_array($user_guid = 0, $site_guid = 0, $flush = false) {
  99. return _elgg_services()->accessCollections->getAccessArray($user_guid, $site_guid, $flush);
  100. }
  101. /**
  102. * Gets the default access permission.
  103. *
  104. * This returns the default access level for the site or optionally of the user.
  105. * If want you to change the default access based on group of other information,
  106. * use the 'default', 'access' plugin hook.
  107. *
  108. * @param ElggUser $user The user for whom we're getting default access. Defaults to logged in user.
  109. * @param array $input_params Parameters passed into an input/access view
  110. *
  111. * @return int default access id (see ACCESS defines in elgglib.php)
  112. */
  113. function get_default_access(ElggUser $user = null, array $input_params = array()) {
  114. global $CONFIG;
  115. // site default access
  116. $default_access = $CONFIG->default_access;
  117. // user default access if enabled
  118. if ($CONFIG->allow_user_default_access) {
  119. $user = $user ? $user : _elgg_services()->session->getLoggedInUser();
  120. if ($user) {
  121. $user_access = $user->getPrivateSetting('elgg_default_access');
  122. if ($user_access !== null) {
  123. $default_access = $user_access;
  124. }
  125. }
  126. }
  127. $params = array(
  128. 'user' => $user,
  129. 'default_access' => $default_access,
  130. 'input_params' => $input_params,
  131. );
  132. return _elgg_services()->hooks->trigger('default', 'access', $params, $default_access);
  133. }
  134. /**
  135. * Allow disabled entities and metadata to be returned by getter functions
  136. *
  137. * @todo Replace this with query object!
  138. * @global bool $ENTITY_SHOW_HIDDEN_OVERRIDE
  139. * @access private
  140. */
  141. $ENTITY_SHOW_HIDDEN_OVERRIDE = false;
  142. /**
  143. * Show or hide disabled entities.
  144. *
  145. * @param bool $show_hidden Show disabled entities.
  146. * @return bool
  147. * @access private
  148. */
  149. function access_show_hidden_entities($show_hidden) {
  150. global $ENTITY_SHOW_HIDDEN_OVERRIDE;
  151. $current_value = $ENTITY_SHOW_HIDDEN_OVERRIDE;
  152. $ENTITY_SHOW_HIDDEN_OVERRIDE = $show_hidden;
  153. return $current_value;
  154. }
  155. /**
  156. * Return current status of showing disabled entities.
  157. *
  158. * @return bool
  159. * @access private
  160. */
  161. function access_get_show_hidden_status() {
  162. global $ENTITY_SHOW_HIDDEN_OVERRIDE;
  163. return $ENTITY_SHOW_HIDDEN_OVERRIDE;
  164. }
  165. /**
  166. * Returns the SQL where clause for enforcing read access to data.
  167. *
  168. * Note that if this code is executed in privileged mode it will return (1=1).
  169. *
  170. * Otherwise it returns a where clause to retrieve the data that a user has
  171. * permission to read.
  172. *
  173. * Plugin authors can hook into the 'get_sql', 'access' plugin hook to modify,
  174. * remove, or add to the where clauses. The plugin hook will pass an array with the current
  175. * ors and ands to the function in the form:
  176. * array(
  177. * 'ors' => array(),
  178. * 'ands' => array()
  179. * )
  180. *
  181. * The results will be combined into an SQL where clause in the form:
  182. * ((or1 OR or2 OR orN) AND (and1 AND and2 AND andN))
  183. *
  184. * @param array $options Array in format:
  185. *
  186. * table_alias => STR Optional table alias. This is based on the select and join clauses.
  187. * Default is 'e'.
  188. *
  189. * user_guid => INT Optional GUID for the user that we are retrieving data for.
  190. * Defaults to the logged in user.
  191. *
  192. * use_enabled_clause => BOOL Optional. Should we append the enabled clause? The default
  193. * is set by access_show_hidden_entities().
  194. *
  195. * access_column => STR Optional access column name. Default is 'access_id'.
  196. *
  197. * owner_guid_column => STR Optional owner_guid column. Default is 'owner_guid'.
  198. *
  199. * guid_column => STR Optional guid_column. Default is 'guid'.
  200. *
  201. * @return string
  202. * @access private
  203. */
  204. function _elgg_get_access_where_sql(array $options = array()) {
  205. return _elgg_services()->accessCollections->getWhereSql($options);
  206. }
  207. /**
  208. * Can a user access an entity.
  209. *
  210. * @warning If a logged in user doesn't have access to an entity, the
  211. * core engine will not load that entity.
  212. *
  213. * @tip This is mostly useful for checking if a user other than the logged in
  214. * user has access to an entity that is currently loaded.
  215. *
  216. * @todo This function would be much more useful if we could pass the guid of the
  217. * entity to test access for. We need to be able to tell whether the entity exists
  218. * and whether the user has access to the entity.
  219. *
  220. * @param \ElggEntity $entity The entity to check access for.
  221. * @param \ElggUser $user Optionally user to check access for. Defaults to
  222. * logged in user (which is a useless default).
  223. *
  224. * @return bool
  225. */
  226. function has_access_to_entity($entity, $user = null) {
  227. return _elgg_services()->accessCollections->hasAccessToEntity($entity, $user);
  228. }
  229. /**
  230. * Returns an array of access permissions that the user is allowed to save content with.
  231. * Permissions returned are of the form (id => 'name').
  232. *
  233. * Example return value in English:
  234. * array(
  235. * 0 => 'Private',
  236. * -2 => 'Friends',
  237. * 1 => 'Logged in users',
  238. * 2 => 'Public',
  239. * 34 => 'My favorite friends',
  240. * );
  241. *
  242. * Plugin hook of 'access:collections:write', 'user'
  243. *
  244. * @warning this only returns access collections that the user owns plus the
  245. * standard access levels. It does not return access collections that the user
  246. * belongs to such as the access collection for a group.
  247. *
  248. * @param int $user_guid The user's GUID.
  249. * @param int $site_guid The current site.
  250. * @param bool $flush If this is set to true, this will ignore a cached access array
  251. * @param array $input_params Some parameters passed into an input/access view
  252. *
  253. * @return array List of access permissions
  254. */
  255. function get_write_access_array($user_guid = 0, $site_guid = 0, $flush = false, array $input_params = array()) {
  256. return _elgg_services()->accessCollections->getWriteAccessArray($user_guid, $site_guid, $flush, $input_params);
  257. }
  258. /**
  259. * Can the user change this access collection?
  260. *
  261. * Use the plugin hook of 'access:collections:write', 'user' to change this.
  262. * @see get_write_access_array() for details on the hook.
  263. *
  264. * Respects access control disabling for admin users and {@link elgg_set_ignore_access()}
  265. *
  266. * @see get_write_access_array()
  267. *
  268. * @param int $collection_id The collection id
  269. * @param mixed $user_guid The user GUID to check for. Defaults to logged in user.
  270. * @return bool
  271. */
  272. function can_edit_access_collection($collection_id, $user_guid = null) {
  273. return _elgg_services()->accessCollections->canEdit($collection_id, $user_guid);
  274. }
  275. /**
  276. * Creates a new access collection.
  277. *
  278. * Access colletions allow plugins and users to create granular access
  279. * for entities.
  280. *
  281. * Triggers plugin hook 'access:collections:addcollection', 'collection'
  282. *
  283. * @note Internal: Access collections are stored in the access_collections table.
  284. * Memberships to collections are in access_collections_membership.
  285. *
  286. * @param string $name The name of the collection.
  287. * @param int $owner_guid The GUID of the owner (default: currently logged in user).
  288. * @param int $site_guid The GUID of the site (default: current site).
  289. *
  290. * @return int|false The collection ID if successful and false on failure.
  291. * @see update_access_collection()
  292. * @see delete_access_collection()
  293. */
  294. function create_access_collection($name, $owner_guid = 0, $site_guid = 0) {
  295. return _elgg_services()->accessCollections->create($name, $owner_guid, $site_guid);
  296. }
  297. /**
  298. * Updates the membership in an access collection.
  299. *
  300. * @warning Expects a full list of all members that should
  301. * be part of the access collection
  302. *
  303. * @note This will run all hooks associated with adding or removing
  304. * members to access collections.
  305. *
  306. * @param int $collection_id The ID of the collection.
  307. * @param array $members Array of member GUIDs
  308. *
  309. * @return bool
  310. * @see add_user_to_access_collection()
  311. * @see remove_user_from_access_collection()
  312. */
  313. function update_access_collection($collection_id, $members) {
  314. return _elgg_services()->accessCollections->update($collection_id, $members);
  315. }
  316. /**
  317. * Deletes a specified access collection and its membership.
  318. *
  319. * @param int $collection_id The collection ID
  320. *
  321. * @return bool
  322. * @see create_access_collection()
  323. * @see update_access_collection()
  324. */
  325. function delete_access_collection($collection_id) {
  326. return _elgg_services()->accessCollections->delete($collection_id);
  327. }
  328. /**
  329. * Get a specified access collection
  330. *
  331. * @note This doesn't return the members of an access collection,
  332. * just the database row of the actual collection.
  333. *
  334. * @see get_members_of_access_collection()
  335. *
  336. * @param int $collection_id The collection ID
  337. *
  338. * @return object|false
  339. */
  340. function get_access_collection($collection_id) {
  341. return _elgg_services()->accessCollections->get($collection_id);
  342. }
  343. /**
  344. * Adds a user to an access collection.
  345. *
  346. * Triggers the 'access:collections:add_user', 'collection' plugin hook.
  347. *
  348. * @param int $user_guid The GUID of the user to add
  349. * @param int $collection_id The ID of the collection to add them to
  350. *
  351. * @return bool
  352. * @see update_access_collection()
  353. * @see remove_user_from_access_collection()
  354. */
  355. function add_user_to_access_collection($user_guid, $collection_id) {
  356. return _elgg_services()->accessCollections->addUser($user_guid, $collection_id);
  357. }
  358. /**
  359. * Removes a user from an access collection.
  360. *
  361. * Triggers the 'access:collections:remove_user', 'collection' plugin hook.
  362. *
  363. * @param int $user_guid The user GUID
  364. * @param int $collection_id The access collection ID
  365. *
  366. * @return bool
  367. * @see update_access_collection()
  368. * @see remove_user_from_access_collection()
  369. */
  370. function remove_user_from_access_collection($user_guid, $collection_id) {
  371. return _elgg_services()->accessCollections->removeUser($user_guid, $collection_id);
  372. }
  373. /**
  374. * Returns an array of database row objects of the access collections owned by $owner_guid.
  375. *
  376. * @param int $owner_guid The entity guid
  377. * @param int $site_guid The GUID of the site (default: current site).
  378. *
  379. * @return array|false
  380. * @see add_access_collection()
  381. * @see get_members_of_access_collection()
  382. */
  383. function get_user_access_collections($owner_guid, $site_guid = 0) {
  384. return _elgg_services()->accessCollections->getEntityCollections($owner_guid, $site_guid);
  385. }
  386. /**
  387. * Get all of members of an access collection
  388. *
  389. * @param int $collection_id The collection's ID
  390. * @param bool $guids_only If set to true, will only return the members' GUIDs (default: false)
  391. *
  392. * @return ElggUser[]|int[]|false guids or entities if successful, false if not
  393. * @see add_user_to_access_collection()
  394. */
  395. function get_members_of_access_collection($collection_id, $guids_only = false) {
  396. return _elgg_services()->accessCollections->getMembers($collection_id, $guids_only);
  397. }
  398. /**
  399. * Return entities based upon access id.
  400. *
  401. * TODO(ewinslow): Move this logic into elgg_get_entities
  402. *
  403. * @param array $options Any options accepted by {@link elgg_get_entities()} and
  404. * access_id => int The access ID of the entity.
  405. *
  406. * @see elgg_get_entities()
  407. * @return mixed If count, int. If not count, array. false on errors.
  408. * @since 1.7.0
  409. */
  410. function elgg_get_entities_from_access_id(array $options = array()) {
  411. // restrict the resultset to access collection provided
  412. if (!isset($options['access_id'])) {
  413. return false;
  414. }
  415. // @todo add support for an array of collection_ids
  416. $where = "e.access_id = '{$options['access_id']}'";
  417. if (isset($options['wheres'])) {
  418. if (is_array($options['wheres'])) {
  419. $options['wheres'][] = $where;
  420. } else {
  421. $options['wheres'] = array($options['wheres'], $where);
  422. }
  423. } else {
  424. $options['wheres'] = array($where);
  425. }
  426. // return entities with the desired options
  427. return _elgg_services()->entityTable->getEntities($options);
  428. }
  429. /**
  430. * Lists entities from an access collection
  431. *
  432. * @param array $options See elgg_list_entities() and elgg_get_entities_from_access_id()
  433. *
  434. * @see elgg_list_entities()
  435. * @see elgg_get_entities_from_access_id()
  436. *
  437. * @return string
  438. */
  439. function elgg_list_entities_from_access_id(array $options = array()) {
  440. return elgg_list_entities($options, 'elgg_get_entities_from_access_id');
  441. }
  442. /**
  443. * Return the name of an ACCESS_* constant or an access collection,
  444. * but only if the logged in user has write access to it.
  445. * Write access requirement prevents us from exposing names of access collections
  446. * that current user has been added to by other members and may contain
  447. * sensitive classification of the current user (e.g. close friends vs acquaintances).
  448. *
  449. * Returns a string in the language of the user for global access levels, e.g.'Public, 'Friends', 'Logged in', 'Public';
  450. * or a name of the owned access collection, e.g. 'My work colleagues';
  451. * or a name of the group or other access collection, e.g. 'Group: Elgg technical support';
  452. * or 'Limited' if the user access is restricted to read-only, e.g. a friends collection the user was added to
  453. *
  454. * @param int $entity_access_id The entity's access id
  455. * @return string
  456. * @since 1.7.0
  457. */
  458. function get_readable_access_level($entity_access_id) {
  459. return _elgg_services()->accessCollections->getReadableAccessLevel($entity_access_id);
  460. }
  461. /**
  462. * Decides if the access system should be ignored for a user.
  463. *
  464. * Returns true (meaning ignore access) if either of these 2 conditions are true:
  465. * 1) an admin user guid is passed to this function.
  466. * 2) {@link elgg_get_ignore_access()} returns true.
  467. *
  468. * @see elgg_set_ignore_access()
  469. *
  470. * @param int $user_guid The user to check against.
  471. *
  472. * @return bool
  473. * @since 1.7.0
  474. * @todo should this be a private function?
  475. */
  476. function elgg_check_access_overrides($user_guid = 0) {
  477. if (!$user_guid || $user_guid <= 0) {
  478. $is_admin = false;
  479. } else {
  480. $is_admin = elgg_is_admin_user($user_guid);
  481. }
  482. return ($is_admin || _elgg_services()->session->getIgnoreAccess());
  483. }
  484. /**
  485. * A flag to set if Elgg's access initialization is finished.
  486. *
  487. * @global bool $init_finished
  488. * @access private
  489. * @todo This is required to tell the access system to start caching because
  490. * calls are made while in ignore access mode and before the user is logged in.
  491. */
  492. $init_finished = false;
  493. /**
  494. * A quick and dirty way to make sure the access permissions have been correctly set up
  495. *
  496. * @elgg_event_handler init system
  497. * @todo Invesigate
  498. *
  499. * @return void
  500. */
  501. function access_init() {
  502. global $init_finished;
  503. $init_finished = true;
  504. }
  505. /**
  506. * Overrides the access system if appropriate.
  507. *
  508. * Allows admin users and calls after {@link elgg_set_ignore_access} to
  509. * bypass the access system.
  510. *
  511. * Registered for the 'permissions_check', 'all' and the
  512. * 'container_permissions_check', 'all' plugin hooks.
  513. *
  514. * Returns true to override the access system or null if no change is needed.
  515. *
  516. * @internal comment upgrade depends on this
  517. *
  518. * @param string $hook
  519. * @param string $type
  520. * @param bool $value
  521. * @param array $params
  522. * @return true|null
  523. * @access private
  524. */
  525. function elgg_override_permissions($hook, $type, $value, $params) {
  526. $user = elgg_extract('user', $params);
  527. if ($user) {
  528. $user_guid = $user->guid;
  529. } else {
  530. $user_guid = _elgg_services()->session->getLoggedInUserGuid();
  531. }
  532. // don't do this so ignore access still works with no one logged in
  533. //if (!$user instanceof \ElggUser) {
  534. // return false;
  535. //}
  536. // check for admin
  537. if ($user_guid && elgg_is_admin_user($user_guid)) {
  538. return true;
  539. }
  540. // check access overrides
  541. if (elgg_check_access_overrides($user_guid)) {
  542. return true;
  543. }
  544. // consult other hooks
  545. return null;
  546. }
  547. /**
  548. * Runs unit tests for the access library
  549. *
  550. * @param string $hook
  551. * @param string $type
  552. * @param array $value
  553. * @param array $params
  554. * @return array
  555. *
  556. * @access private
  557. */
  558. function access_test($hook, $type, $value, $params) {
  559. global $CONFIG;
  560. $value[] = $CONFIG->path . 'engine/tests/ElggCoreAccessCollectionsTest.php';
  561. $value[] = $CONFIG->path . 'engine/tests/ElggCoreAccessSQLTest.php';
  562. return $value;
  563. }
  564. return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
  565. // Tell the access functions the system has booted, plugins are loaded,
  566. // and the user is logged in so it can start caching
  567. $events->registerHandler('ready', 'system', 'access_init');
  568. // For overrided permissions
  569. $hooks->registerHandler('permissions_check', 'all', 'elgg_override_permissions');
  570. $hooks->registerHandler('container_permissions_check', 'all', 'elgg_override_permissions');
  571. $hooks->registerHandler('unit_test', 'system', 'access_test');
  572. };