Skip to content

Commit

Permalink
Merge pull request #8179 from juho-jaakkola/merge_1x_to_master
Browse files Browse the repository at this point in the history
Merge 1.x to master
  • Loading branch information
mrclay committed Apr 12, 2015
2 parents 6598823 + b1b9723 commit 3398a07
Show file tree
Hide file tree
Showing 67 changed files with 849 additions and 548 deletions.
18 changes: 15 additions & 3 deletions docs/guides/actions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ Security Tokens
On occasion we need to pass data through an untrusted party or generate an "unguessable token" based on some data.
The industry-standard `HMAC <http://security.stackexchange.com/a/20301/4982>`_ algorithm is the right tool for this.
It allows us to verify that received data were generated by our site, and were not tampered with. Note that even
strong hash functions like SHA-2 should *not* be used directly for these tasks.
strong hash functions like SHA-2 should *not* be used without HMAC for these tasks.

Elgg provides ``elgg_build_hmac()`` to generate and validate HMAC message authentication codes that are unguessable
without the site's private key.
Expand All @@ -449,10 +449,22 @@ without the site's private key.
// validate the querystring
$a = get_input('a', '', false);
$b = get_input('b', '', false);
$a = (int) get_input('a', '', false);
$b = (string) get_input('b', '', false);
$mac = get_input('mac', '', false);
if (elgg_build_hmac([$a, $b])->matchesToken($mac)) {
// $a and $b have not been altered
}
Note: If you use a non-string as HMAC data, you must use types consistently. Consider the following:

.. code:: php
$mac = elgg_build_hmac([123, 456])->getToken();
// type of first array element differs
elgg_build_hmac(["123", 456])->matchesToken($mac); // false
// types identical to original
elgg_build_hmac([123, 456])->matchesToken($mac); // true
6 changes: 6 additions & 0 deletions docs/guides/hooks-list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ Permission hooks
**permissions_check, <entity_type>**
Return boolean for if the user ``$params['user']`` can edit the entity ``$params['entity']``.

**permissions_check:delete, <entity_type>**
Return boolean for if the user ``$params['user']`` can delete the entity ``$params['entity']``. Defaults to ``$entity->canEdit()``.

**permissions_check, widget_layout**
Return boolean for if ``$params['user']`` can edit the widgets in the context passed as
``$params['context']`` and with a page owner of ``$params['page_owner']``.
Expand Down Expand Up @@ -453,6 +456,9 @@ Other
**robots.txt, site**
Filter the robots.txt values for ``$params['site']``.

**config, amd**
Filter the AMD config for the requirejs library.

Plugins
=======

Expand Down
2 changes: 1 addition & 1 deletion engine/classes/Elgg/ActionsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ public function generateActionToken($timestamp) {

$session_token = _elgg_services()->session->get('__elgg_session');

return _elgg_services()->crypto->getHmac([$timestamp, $session_id, $session_token], 'md5')
return _elgg_services()->crypto->getHmac([(int)$timestamp, $session_id, $session_token], 'md5')
->getToken();
}

Expand Down
23 changes: 22 additions & 1 deletion engine/classes/Elgg/Amd/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ class Config {
private $shim = array();
private $dependencies = array();

/**
* @var \Elgg\PluginHooksService
*/
private $hooks;

/**
* Constructor
*
* @param \Elgg\PluginHooksService $hooks The hooks service
*/
public function __construct(\Elgg\PluginHooksService $hooks) {
$this->hooks = $hooks;
}

/**
* Set the base URL for the site
*
Expand Down Expand Up @@ -226,11 +240,18 @@ public function hasModule($name) {
* @return array
*/
public function getConfig() {
return array(
$defaults = array(
'baseUrl' => $this->baseUrl,
'paths' => $this->paths,
'shim' => $this->shim,
'deps' => $this->getDependencies(),
'waitSeconds' => 20,
);

$params = array(
'defaults' => $defaults
);

return $this->hooks->trigger('config', 'amd', $params, $defaults);
}
}
40 changes: 22 additions & 18 deletions engine/classes/Elgg/CacheHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,28 +138,32 @@ protected function setupSimplecache() {
return;
}

$dblink = mysql_connect($this->config->dbhost, $this->config->dbuser, $this->config->dbpass, true);
if (!$dblink) {
$this->send403('Cache error: unable to connect to database server');
}

if (!mysql_select_db($this->config->dbname, $dblink)) {
$this->send403('Cache error: unable to connect to Elgg database');
$db_config = new Database\Config($this->config);
$db = new Database($db_config, new Logger(new PluginHooksService()));

try {
$rows = $db->getData("
SELECT `name`, `value`
FROM {$db->getTablePrefix()}datalists
WHERE `name` IN ('dataroot', 'simplecache_enabled')
");
if (!$rows) {
$this->send403('Cache error: unable to get the data root');
}
} catch (\DatabaseException $e) {
if (0 === strpos($e->getMessage(), "Elgg couldn't connect")) {
$this->send403('Cache error: unable to connect to database server');
} else {
$this->send403('Cache error: unable to connect to Elgg database');
}
exit; // unnecessary, but helps PhpStorm understand
}

$query = "SELECT `name`, `value` FROM {$this->config->dbprefix}datalists
WHERE `name` IN ('dataroot', 'simplecache_enabled')";

$result = mysql_query($query, $dblink);
if ($result) {
while ($row = mysql_fetch_object($result)) {
$this->config->{$row->name} = $row->value;
}
mysql_free_result($result);
foreach ($rows as $row) {
$this->config->{$row->name} = $row->value;
}
mysql_close($dblink);

if (!$result || !isset($this->config->dataroot)) {
if (empty($this->config->dataroot)) {
$this->send403('Cache error: unable to get the data root');
}
}
Expand Down
12 changes: 12 additions & 0 deletions engine/classes/Elgg/Database.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php
namespace Elgg;
use Elgg\Database\Config;

/**
* An object representing a single Elgg database.
Expand Down Expand Up @@ -638,5 +639,16 @@ public function sanitizeInt($value, $signed = true) {
public function sanitizeString($value) {
return mysql_real_escape_string($value);
}

/**
* Get the server version number
*
* @param string $type Connection type (Config constants, e.g. Config::READ_WRITE)
*
* @return string
*/
public function getServerVersion($type) {
return mysql_get_server_info($this->getLink($type));
}
}

72 changes: 60 additions & 12 deletions engine/classes/Elgg/Database/AccessCollections.php
Original file line number Diff line number Diff line change
Expand Up @@ -395,20 +395,13 @@ function getWriteAccessArray($user_guid = 0, $site_guid = 0, $flush = false, arr
} else {
// @todo is there such a thing as public write access?
$access_array = array(
ACCESS_PRIVATE => _elgg_services()->translator->translate("PRIVATE"),
ACCESS_FRIENDS => _elgg_services()->translator->translate("access:friends:label"),
ACCESS_LOGGED_IN => _elgg_services()->translator->translate("LOGGED_IN"),
ACCESS_PUBLIC => _elgg_services()->translator->translate("PUBLIC")
ACCESS_PRIVATE => $this->getReadableAccessLevel(ACCESS_PRIVATE),
ACCESS_FRIENDS => $this->getReadableAccessLevel(ACCESS_FRIENDS),
ACCESS_LOGGED_IN => $this->getReadableAccessLevel(ACCESS_LOGGED_IN),
ACCESS_PUBLIC => $this->getReadableAccessLevel(ACCESS_PUBLIC)
);

$db = _elgg_services()->db;
$prefix = $db->getTablePrefix();

$query = "SELECT ag.* FROM {$prefix}access_collections ag ";
$query .= " WHERE (ag.site_guid = $site_guid OR ag.site_guid = 0)";
$query .= " AND (ag.owner_guid = $user_guid)";

$collections = $db->getData($query);
$collections = $this->getEntityCollections($user_guid, $site_guid);
if ($collections) {
foreach ($collections as $collection) {
$access_array[$collection->id] = $collection->name;
Expand Down Expand Up @@ -792,4 +785,59 @@ function getCollectionsByMember($member_guid, $site_guid = 0) {

return $collections;
}

/**
* Return the name of an ACCESS_* constant or an access collection,
* but only if the logged in user owns the access collection or is an admin.
* Ownership requirement prevents us from exposing names of access collections
* that current user has been added to by other members and may contain
* sensitive classification of the current user (e.g. close friends vs acquaintances).
*
* Returns a string in the language of the user for global access levels, e.g.'Public, 'Friends', 'Logged in', 'Private';
* or a name of the owned access collection, e.g. 'My work colleagues';
* or a name of the group or other access collection, e.g. 'Group: Elgg technical support';
* or 'Limited' if the user access is restricted to read-only, e.g. a friends collection the user was added to
*
* @param int $entity_access_id The entity's access id
*
* @return string
* @since 1.11
*/
function getReadableAccessLevel($entity_access_id) {
$access = (int) $entity_access_id;

$translator = _elgg_services()->translator;

// Check if entity access id is a defined global constant
$access_array = array(
ACCESS_PRIVATE => $translator->translate("PRIVATE"),
ACCESS_FRIENDS => $translator->translate("access:friends:label"),
ACCESS_LOGGED_IN => $translator->translate("LOGGED_IN"),
ACCESS_PUBLIC => $translator->translate("PUBLIC"),
);

if (array_key_exists($access, $access_array)) {
return $access_array[$access];
}

$user_guid = _elgg_services()->session->getLoggedInUserGuid();
if (!$user_guid) {
// return 'Limited' if there is no logged in user
return $translator->translate('access:limited:label');
}

// Entity access id is probably a custom access collection
// Check if the user has write access to it and can see it's label
// Admins should always be able to see the readable version
$collection = $this->get($access);

if ($collection) {
if (($collection->owner_guid == $user_guid) || _elgg_services()->session->isAdminLoggedIn()) {
return $collection->name;
}
}

// return 'Limited' if the user does not have access to the access collection
return $translator->translate('access:limited:label');
}
}
4 changes: 2 additions & 2 deletions engine/classes/Elgg/Database/UsersTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ function register($username, $password, $name, $email, $allow_multiple_emails =
*/
function generateInviteCode($username) {
$time = time();
return "$time." . _elgg_services()->crypto->getHmac([$time, $username])->getToken();
return "$time." . _elgg_services()->crypto->getHmac([(int)$time, $username])->getToken();
}

/**
Expand All @@ -466,7 +466,7 @@ function validateInviteCode($username, $code) {
$time = $m[1];
$mac = $m[2];

return _elgg_services()->crypto->getHmac([$time, $username])->matchesToken($mac);
return _elgg_services()->crypto->getHmac([(int)$time, $username])->matchesToken($mac);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion engine/classes/Elgg/Di/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public function __construct(\Elgg\AutoloadManager $autoload_manager) {
$this->setClassName('adminNotices', '\Elgg\Database\AdminNotices');

$this->setFactory('amdConfig', function(ServiceProvider $c) {
$obj = new \Elgg\Amd\Config();
$obj = new \Elgg\Amd\Config($c->hooks);
$obj->setBaseUrl($c->simpleCache->getRoot() . "js/");
return $obj;
});
Expand Down
14 changes: 6 additions & 8 deletions engine/classes/Elgg/Security/Hmac.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,19 @@ class Hmac {
/**
* Constructor
*
* @param string $key HMAC key
* @param callable $comparator Function that returns true if given two equal strings, else false
* @param string[]|string $data HMAC data string (or array of strings)
* @param string $algo Hash algorithm
* @param string $key HMAC key
* @param callable $comparator Function that returns true if given two equal strings, else false
* @param mixed $data HMAC data string or serializable data
* @param string $algo Hash algorithm
*/
public function __construct($key, callable $comparator, $data, $algo = 'sha256') {
$this->key = $key;
$this->comparator = $comparator;
if (!$data) {
throw new \InvalidArgumentException('$data cannot be empty');
}
if (is_array($data)) {
$data = json_encode(array_map('strval', $data));
} else {
$data = (string)$data;
if (!is_string($data)) {
$data = serialize($data);
}
$this->data = $data;
$this->algo = $algo;
Expand Down
6 changes: 3 additions & 3 deletions engine/classes/ElggCrypto.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@ public function getRandomBytes($length) {
/**
* Get an HMAC token builder/validator object
*
* @param string[]|string $data HMAC data, or array of strings to use as data
* @param string $algo Hash algorithm
* @param string $key Optional key (default uses site secret)
* @param mixed $data HMAC data or serializable data
* @param string $algo Hash algorithm
* @param string $key Optional key (default uses site secret)
*
* @return \Elgg\Security\Hmac
*/
Expand Down
45 changes: 43 additions & 2 deletions engine/classes/ElggEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,47 @@ public function canEdit($user_guid = 0) {
return _elgg_services()->hooks->trigger('permissions_check', $this->type, $params, $return);
}

/**
* Can a user delete this entity?
*
* @tip Can be overridden by registering for the permissions_check:delete plugin hook.
*
* @param int $user_guid The user GUID, optionally (default: logged in user)
*
* @return bool Whether this entity is deletable by the given user.
* @since 1.11
* @see elgg_set_ignore_access()
*/
public function canDelete($user_guid = 0) {
$user_guid = (int) $user_guid;

if (!$user_guid) {
$user_guid = _elgg_services()->session->getLoggedInUserGuid();
}

// need to ignore access and show hidden entities for potential hidden/disabled users
$ia = elgg_set_ignore_access(true);
$show_hidden = access_show_hidden_entities(true);

$user = _elgg_services()->entityTable->get($user_guid, 'user');

elgg_set_ignore_access($ia);
access_show_hidden_entities($show_hidden);

if ($user_guid & !$user) {
// requested to check access for a specific user_guid, but there is no user entity, so return false
$message = _elgg_services()->translator->translate('entity:can_delete:invaliduser', array($user_guid));
_elgg_services()->logger->warning($message);

return false;
}

$return = $this->canEdit($user_guid);

$params = array('entity' => $this, 'user' => $user);
return _elgg_services()->hooks->trigger('permissions_check:delete', $this->type, $params, $return);
}

/**
* Can a user edit metadata on this entity?
*
Expand Down Expand Up @@ -1993,10 +2034,10 @@ public function delete($recursive = true) {
return false;
}

// first check if we have edit access to this entity
// first check if we can delete this entity
// NOTE: in Elgg <= 1.10.3 this was after the delete event,
// which could potentially remove some content if the user didn't have access
if (!$this->canEdit()) {
if (!$this->canDelete()) {
return false;
}

Expand Down

0 comments on commit 3398a07

Please sign in to comment.