Skip to content

Commit

Permalink
more acl work
Browse files Browse the repository at this point in the history
  • Loading branch information
jdalsem committed Nov 30, 2017
1 parent b4bdda6 commit 6d265b1
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 42 deletions.
1 change: 1 addition & 0 deletions docs/appendix/upgrade-notes/2.x-to-3.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ All the functions in ``engine/lib/deprecated-1.10.php`` were removed. See https:
* ``pages_can_delete_page``: Use ``$entity->canDelete()``
* ``pages_search_pages``
* ``pages_is_page``: use ``$entity instanceof ElggPage``
* ``update_river_access_by_object``: Logic moved into ``ElggEntity`` and should not be accessed directly

All functions around entity subtypes table:
* ``add_subtype``: Use ``elgg_set_entity_class`` at runtime
Expand Down
2 changes: 1 addition & 1 deletion engine/classes/Elgg/Database/AccessCollections.php
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@ public function getReadableAccessLevel($entity_access_id) {
$collection = $this->get($access);

$user_guid = $this->session->getLoggedInUserGuid();

if (!$collection || !$collection->canEdit()) {
// return 'Limited' if there is collection can not be loaded or it can not be edited
return $translator->translate('access:label:limited');
Expand Down
58 changes: 54 additions & 4 deletions engine/classes/ElggEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,28 @@ public function getOwnedAccessCollections($options = []) {
$options['owner_guid'] = $this->guid;
return _elgg_services()->accessCollections->getEntityCollections($options);
}

/**
* Returns the first ACL owned by the entity with a given subtype
*
* @param string $subtype subtype of the ACL
*
* @return \ElggAccessCollection|false
*
* @since 3.0
*/
public function getOwnedAccessCollection($subtype) {
if (!is_string($subtype)) {
return false;
}

$options = [
'owner_guid' => $this->guid,
'subtype' => $subtype,
];

return elgg_extract(0, _elgg_services()->accessCollections->getEntityCollections($options), false);
}

/**
* Gets an array of entities with a relationship to this entity.
Expand Down Expand Up @@ -1319,7 +1341,11 @@ protected function create() {
$container_guid = (int) $container_guid;

if ($access_id == ACCESS_DEFAULT) {
throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h');
throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.php');
}

if ($access_id == ACCESS_FRIENDS) {
throw new \InvalidParameterException('ACCESS_FRIENDS is not a valid access level. See its documentation in elgglib.php');
}

$user_guid = elgg_get_logged_in_user_guid();
Expand Down Expand Up @@ -1454,6 +1480,10 @@ protected function update() {
throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.php');
}

if ($access_id == ACCESS_FRIENDS) {
throw new \InvalidParameterException('ACCESS_FRIENDS is not a valid access level. See its documentation in elgglib.php');
}

// Update primary table
$ret = _elgg_services()->entityTable->updateRow($guid, (object) [
'owner_guid' => $owner_guid,
Expand All @@ -1471,16 +1501,36 @@ protected function update() {

elgg_trigger_after_event('update', $this->type, $this);

// TODO(evan): Move this to \ElggObject?
if ($this instanceof \ElggObject) {
update_river_access_by_object($guid, $access_id);
if ($access_id !== elgg_extract('access_id', $this->orig_attributes)) {
$this->updateRiverAccessID();
}

$this->orig_attributes = [];

// Handle cases where there was no error BUT no rows were updated!
return true;
}

/**
* Sets the access ID on river items where the current entity is set as the object to the current access_id
*
* @return bool Depending on success
*/
protected function updateRiverAccessID() {
$dbprefix = _elgg_config()->dbprefix;
$query = "
UPDATE {$dbprefix}river
SET access_id = :access_id
WHERE object_guid = :object_guid
";

$params = [
':access_id' => (int) $this->access_id,
':object_guid' => (int) $this->guid,
];

return update_data($query, $params);
}

/**
* Loads attributes from the entities table into the object.
Expand Down
106 changes: 106 additions & 0 deletions engine/lib/access.php
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,106 @@ function access_init() {
_elgg_services()->accessCollections->markInitComplete();
}

/**
* Creates a Friends ACL for a user
*
* @param \Elgg\Event $event event
*
* @return void
*/
function access_friends_acl_create(\Elgg\Event $event) {
$user = $event->getObject();
if (!($user instanceof \ElggUser)) {
return;
}

create_access_collection('friends', $user->guid, 'friends');
}

/**
* Adds the friend to the user friend ACL
*
* @param \Elgg\Event $event event
*
* @return void
*/
function access_friends_acl_add_friend(\Elgg\Event $event) {
$relationship_object = $event->getObject();
if (!($relationship_object instanceof \ElggRelationship)) {
return;
}

if ($relationship_object->relationship !== 'friend') {
return;
}

$user = get_user($relationship_object->guid_one);
$friend = get_user($relationship_object->guid_two);

if (!$user || !$friend) {
return;
}

$acl = $user->getOwnedAccessCollection('friends');
if (empty($acl)) {
return;
}

$acl->addMember($friend->guid);
}

/**
* Add the friend to the user friends ACL
*
* @param \Elgg\Event $event event
*
* @return void
*/
function access_friends_acl_remove_friend(\Elgg\Event $event) {
$relationship_object = $event->getObject();
if (!($relationship_object instanceof \ElggRelationship)) {
return;
}

if ($relationship_object->relationship !== 'friend') {
return;
}

$user = get_user($relationship_object->guid_one);
$friend = get_user($relationship_object->guid_two);

if (!$user || !$friend) {
return;
}

$acl = $user->getOwnedAccessCollection('friends');
if (empty($acl)) {
return;
}

$acl->removeMember($friend->guid);
}

/**
* Return the name of a friends ACL
*
* @param \Elgg\Hook $hook hook
*
* @return string|void
*/
function access_friends_acl_get_name(\Elgg\Hook $hook) {
$access_collection = $hook->getParam('access_collection');
if (!($access_collection instanceof ElggAccessCollection)) {
return;
}

if ($access_collection->getSubtype() !== 'friends') {
return;
}

return elgg_echo('access:friends:label');
}

/**
* Runs unit tests for the access library
*
Expand All @@ -438,5 +538,11 @@ function access_test($hook, $type, $value, $params) {
// and the user is logged in so it can start caching
$events->registerHandler('ready', 'system', 'access_init');

// friends ACL events
$events->registerHandler('create', 'user', 'access_friends_acl_create');
$events->registerHandler('create', 'relationship', 'access_friends_acl_add_friend');
$events->registerHandler('delete', 'relationship', 'access_friends_acl_remove_friend');
$hooks->registerHandler('access_collection:name', 'access_collection', 'access_friends_acl_get_name');

$hooks->registerHandler('unit_test', 'system', 'access_test');
};
4 changes: 2 additions & 2 deletions engine/lib/constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
/**
* Controls access levels on \ElggEntity entities, metadata, and annotations.
*
* @warning ACCESS_DEFAULT is a place holder for the input/access view. Do not
* use it when saving an entity.
* @warning ACCESS_DEFAULT and ACCESS_FRIENDS are placeholders for the input/access view.
* Do not use them when saving or updating an entity.
*
* @var int
*/
Expand Down
25 changes: 0 additions & 25 deletions engine/lib/river.php
Original file line number Diff line number Diff line change
Expand Up @@ -361,31 +361,6 @@ function elgg_list_river(array $options = []) {
return elgg_view('page/components/list', $options);
}

/**
* Sets the access ID on river items for a particular object
*
* @param int $object_guid The GUID of the entity
* @param int $access_id The access ID
*
* @return bool Depending on success
*/
function update_river_access_by_object($object_guid, $access_id) {

$dbprefix = _elgg_config()->dbprefix;
$query = "
UPDATE {$dbprefix}river
SET access_id = :access_id
WHERE object_guid = :object_guid
";

$params = [
':access_id' => (int) $access_id,
':object_guid' => (int) $object_guid,
];

return update_data($query, $params);
}

/**
* Register river unit tests
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

use Phinx\Migration\AbstractMigration;

class CreateFriendsAccessCollections extends AbstractMigration {
public function up() {
if (!$this->hasTable('entities') || !$this->hasTable('access_collections') || !$this->hasTable('access_collection_membership')) {
return;
}

$prefix = $this->getAdapter()->getOption('table_prefix');

$users_query = "
SELECT e.* FROM {$prefix}entities e
WHERE e.type = 'user'
AND e.guid NOT IN (
SELECT ac.owner_guid FROM {$prefix}access_collections ac
WHERE ac.owner_guid = e.guid
AND ac.subtype = 'friends'
)
LIMIT 25
";

while ($users = $this->fetchAll($users_query)) {
foreach ($users as $user) {
// create access collection for user friends
$this->insert('access_collections', [
'name' => 'friends',
'owner_guid' => $user['guid'],
'subtype' => 'friends',
]);

// retrieve just created acl
$access_collection_query = "
SELECT * FROM {$prefix}access_collections
WHERE owner_guid = {$user['guid']}
AND subtype = 'friends'
";

$acl = $this->fetchRow($access_collection_query);

// get users friends
$friends_query = "
SELECT guid_two AS guid FROM {$prefix}entity_relationships
WHERE guid_one = {$user['guid']}
AND relationship = 'friend'
";
$friends = $this->fetchAll($friends_query);
foreach ($friends as $friend) {
// add friend to user access collection
$this->insert('access_collection_membership', [
'access_collection_id' => $acl['id'],
'user_guid' => $friend['guid'],
]);
}
}
}
}
}
11 changes: 7 additions & 4 deletions languages/en.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,19 +184,22 @@
* Access
*/

'access' => "Who can see this",

'access:label:private' => "Private",
'access:label:logged_in' => "Logged in users",
'access:label:public' => "Public",
'access:label:logged_out' => "Logged out users",
'access:label:friends' => "Friends",
'access' => "Who can see this",

'access:label:limited' => "Limited",
'access:label:admin_only' => "Administrators only",
'access:label:missing_name' => "Missing access level name",

'access:overridenotice' => "Note: Due to group policy, this content will be accessible only by group members.",
'access:limited:label' => "Limited",
'access:help' => "The access level",
'access:read' => "Read access",
'access:write' => "Write access",
'access:admin_only' => "Administrators only",
'access:missing_name' => "Missing access level name",
'access:comments:change' => "This discussion is currently visible to a limited audience. Be thoughtful about who you share it with.",

/**
Expand Down
10 changes: 8 additions & 2 deletions mod/friends/start.php
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ function _elgg_send_friend_notification($event, $type, $object) {
}

/**
* Add ACCESS_FRIENDS to the available access levels
* Add Friends ACL to the available access levels
*
* @param string $hook "access:collections:write"
* @param string $type "user"
Expand All @@ -243,7 +243,13 @@ function _elgg_friends_write_access($hook, $type, $access_array, $params) {
}

// friends
$ret[ACCESS_FRIENDS] = get_readable_access_level(ACCESS_FRIENDS);
$user = elgg_get_logged_in_user_entity();
if ($user) {
$friends_acl = $user->getOwnedAccessCollection('friends');
if ($friends_acl) {
$ret[$friends_acl->id] = $friends_acl->getDisplayName();
}
}

// rest
foreach ($access_array as $key => $value) {
Expand Down
2 changes: 1 addition & 1 deletion views/default/input/access.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
// if access is set to a value not present in the available options, add the option
if (!isset($vars['options_values'][$vars['value']])) {
$acl = get_access_collection($vars['value']);
$display = $acl ? $acl->name : elgg_echo('access:missing_name');
$display = $acl ? $acl->name : elgg_echo('access:label:missing_name');
$vars['options_values'][$vars['value']] = $display;
}

Expand Down

0 comments on commit 6d265b1

Please sign in to comment.