MDL-58010 user: allow to update only whitelisted preferences
This commit is contained in:
committed by
Dan Poltawski
parent
10d80cd64c
commit
0833e7cfc7
@@ -86,7 +86,7 @@ function block_course_overview_get_myorder() {
|
||||
// If preference was not found, look in the old location and convert if found.
|
||||
$order = array();
|
||||
if ($value = get_user_preferences('course_overview_course_order')) {
|
||||
$order = unserialize($value);
|
||||
$order = unserialize_array($value);
|
||||
block_course_overview_update_myorder($order);
|
||||
unset_user_preference('course_overview_course_order');
|
||||
}
|
||||
|
||||
@@ -1758,7 +1758,7 @@ class grade_report_grader extends grade_report {
|
||||
|
||||
// Try looking for old location of user setting that used to store all courses in one serialized user preference.
|
||||
if (($oldcollapsedpref = get_user_preferences('grade_report_grader_collapsed_categories')) !== null) {
|
||||
if ($collapsedall = @unserialize($oldcollapsedpref)) {
|
||||
if ($collapsedall = unserialize_array($oldcollapsedpref)) {
|
||||
// We found the old-style preference, filter out only categories that belong to this course and update the prefs.
|
||||
$collapsed = static::filter_collapsed_categories($courseid, $collapsedall);
|
||||
if (!empty($collapsed['aggregatesonly']) || !empty($collapsed['gradesonly'])) {
|
||||
|
||||
@@ -9512,6 +9512,58 @@ function get_course_display_name_for_list($course) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe analogue of unserialize() that can only parse arrays
|
||||
*
|
||||
* Arrays may contain only integers or strings as both keys and values. Nested arrays are allowed.
|
||||
* Note: If any string (key or value) has semicolon (;) as part of the string parsing will fail.
|
||||
* This is a simple method to substitute unnecessary unserialize() in code and not intended to cover all possible cases.
|
||||
*
|
||||
* @param string $expression
|
||||
* @return array|bool either parsed array or false if parsing was impossible.
|
||||
*/
|
||||
function unserialize_array($expression) {
|
||||
$subs = [];
|
||||
// Find nested arrays, parse them and store in $subs , substitute with special string.
|
||||
while (preg_match('/([\^;\}])(a:\d+:\{[^\{\}]*\})/', $expression, $matches) && strlen($matches[2]) < strlen($expression)) {
|
||||
$key = '--SUB' . count($subs) . '--';
|
||||
$subs[$key] = unserialize_array($matches[2]);
|
||||
if ($subs[$key] === false) {
|
||||
return false;
|
||||
}
|
||||
$expression = str_replace($matches[2], $key . ';', $expression);
|
||||
}
|
||||
|
||||
// Check the expression is an array.
|
||||
if (!preg_match('/^a:(\d+):\{([^\}]*)\}$/', $expression, $matches1)) {
|
||||
return false;
|
||||
}
|
||||
// Get the size and elements of an array (key;value;key;value;....).
|
||||
$parts = explode(';', $matches1[2]);
|
||||
$size = intval($matches1[1]);
|
||||
if (count($parts) < $size * 2 + 1) {
|
||||
return false;
|
||||
}
|
||||
// Analyze each part and make sure it is an integer or string or a substitute.
|
||||
$value = [];
|
||||
for ($i = 0; $i < $size * 2; $i++) {
|
||||
if (preg_match('/^i:(\d+)$/', $parts[$i], $matches2)) {
|
||||
$parts[$i] = (int)$matches2[1];
|
||||
} else if (preg_match('/^s:(\d+):"(.*)"$/', $parts[$i], $matches3) && strlen($matches3[2]) == (int)$matches3[1]) {
|
||||
$parts[$i] = $matches3[2];
|
||||
} else if (preg_match('/^--SUB\d+--$/', $parts[$i])) {
|
||||
$parts[$i] = $subs[$parts[$i]];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Combine keys and values.
|
||||
for ($i = 0; $i < $size * 2; $i += 2) {
|
||||
$value[$parts[$i]] = $parts[$i+1];
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The lang_string class
|
||||
*
|
||||
|
||||
@@ -3036,4 +3036,34 @@ class core_moodlelib_testcase extends advanced_testcase {
|
||||
$CFG->mailprefix = 'mdl-';
|
||||
$this->assertEquals(1, validate_email(generate_email_processing_address(23, $modargs)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test safe method unserialize_array().
|
||||
*/
|
||||
public function test_unserialize_array() {
|
||||
$a = [1, 2, 3];
|
||||
$this->assertEquals($a, unserialize_array(serialize($a)));
|
||||
$this->assertEquals($a, unserialize_array(serialize($a)));
|
||||
$a = ['a' => 1, 2 => 2, 'b' => 'cde'];
|
||||
$this->assertEquals($a, unserialize_array(serialize($a)));
|
||||
$this->assertEquals($a, unserialize_array(serialize($a)));
|
||||
$a = ['a' => 1, 2 => 2, 'b' => 'c"d"e'];
|
||||
$this->assertEquals($a, unserialize_array(serialize($a)));
|
||||
$a = ['a' => 1, 2 => ['c' => 'd', 'e' => 'f'], 'b' => 'cde'];
|
||||
$this->assertEquals($a, unserialize_array(serialize($a)));
|
||||
|
||||
// Can not unserialize if any string contains semicolons.
|
||||
$a = ['a' => 1, 2 => 2, 'b' => 'c"d";e'];
|
||||
$this->assertEquals(false, unserialize_array(serialize($a)));
|
||||
|
||||
// Can not unserialize if there are any objects.
|
||||
$a = (object)['a' => 1, 2 => 2, 'b' => 'cde'];
|
||||
$this->assertEquals(false, unserialize_array(serialize($a)));
|
||||
$a = ['a' => 1, 2 => 2, 'b' => (object)['a' => 'cde']];
|
||||
$this->assertEquals(false, unserialize_array(serialize($a)));
|
||||
|
||||
// Array used in the grader report.
|
||||
$a = array('aggregatesonly' => [51, 34], 'gradesonly' => [21, 45, 78]);
|
||||
$this->assertEquals($a, unserialize_array(serialize($a)));
|
||||
}
|
||||
}
|
||||
|
||||
+32
-3
@@ -217,8 +217,8 @@ class core_user_external extends external_api {
|
||||
profile_save_data((object) $user);
|
||||
}
|
||||
|
||||
$userobject = (object)$user;
|
||||
if ($createpassword) {
|
||||
$userobject = (object)$user;
|
||||
setnew_password_and_mail($userobject);
|
||||
unset_user_preference('create_password', $userobject);
|
||||
set_user_preference('auth_forcepasswordchange', 1, $userobject);
|
||||
@@ -230,7 +230,7 @@ class core_user_external extends external_api {
|
||||
// Preferences.
|
||||
if (!empty($user['preferences'])) {
|
||||
foreach ($user['preferences'] as $preference) {
|
||||
set_user_preference($preference['type'], $preference['value'], $user['id']);
|
||||
self::set_user_preference($preference['type'], $preference['value'], $userobject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,7 +459,7 @@ class core_user_external extends external_api {
|
||||
// Preferences.
|
||||
if (!empty($user['preferences'])) {
|
||||
foreach ($user['preferences'] as $preference) {
|
||||
set_user_preference($preference['type'], $preference['value'], $user['id']);
|
||||
self::set_user_preference($preference['type'], $preference['value'], $existinguser);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1453,6 +1453,35 @@ class core_user_external extends external_api {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates preference value and updates the user preference
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param stdClass $user
|
||||
*/
|
||||
protected static function set_user_preference($name, $value, $user) {
|
||||
$preferences = array(
|
||||
'auth_forcepasswordchange' => PARAM_BOOL,
|
||||
'htmleditor' => PARAM_COMPONENT,
|
||||
'usemodchooser' => PARAM_BOOL,
|
||||
'badgeprivacysetting' => PARAM_BOOL,
|
||||
'blogpagesize' => PARAM_INT,
|
||||
'forum_markasreadonnotification' => PARAM_INT,
|
||||
'calendar_timeformat' => PARAM_NOTAGS,
|
||||
'calendar_startwday' => PARAM_INT,
|
||||
'calendar_maxevents' => PARAM_INT,
|
||||
'calendar_lookahead' => PARAM_INT,
|
||||
'calendar_persistflt' => PARAM_INT
|
||||
);
|
||||
if (isset($preferences[$name])) {
|
||||
$value = clean_param($value, $preferences[$name]);
|
||||
if ($preferences[$name] == PARAM_BOOL) {
|
||||
$value = (int)$value;
|
||||
}
|
||||
set_user_preference($name, $value, $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -497,7 +497,14 @@ class core_user_externallib_testcase extends externallib_advanced_testcase {
|
||||
'email' => '[email protected]',
|
||||
'description' => 'This is a description for user 1',
|
||||
'city' => 'Perth',
|
||||
'country' => 'au'
|
||||
'country' => 'au',
|
||||
'preferences' => [[
|
||||
'type' => 'htmleditor',
|
||||
'value' => 'atto'
|
||||
], [
|
||||
'type' => 'invalidpreference',
|
||||
'value' => 'abcd'
|
||||
]]
|
||||
);
|
||||
|
||||
$context = context_system::instance();
|
||||
@@ -522,6 +529,8 @@ class core_user_externallib_testcase extends externallib_advanced_testcase {
|
||||
$this->assertEquals($dbuser->description, $user1['description']);
|
||||
$this->assertEquals($dbuser->city, $user1['city']);
|
||||
$this->assertEquals($dbuser->country, $user1['country']);
|
||||
$this->assertEquals('atto', get_user_preferences('htmleditor', null, $dbuser));
|
||||
$this->assertEquals(null, get_user_preferences('invalidpreference', null, $dbuser));
|
||||
}
|
||||
|
||||
// Call without required capability
|
||||
@@ -676,7 +685,14 @@ class core_user_externallib_testcase extends externallib_advanced_testcase {
|
||||
'email' => '[email protected]',
|
||||
'description' => 'This is a description for user 1',
|
||||
'city' => 'Perth',
|
||||
'country' => 'au'
|
||||
'country' => 'au',
|
||||
'preferences' => [[
|
||||
'type' => 'htmleditor',
|
||||
'value' => 'atto'
|
||||
], [
|
||||
'type' => 'invalidpreference',
|
||||
'value' => 'abcd'
|
||||
]]
|
||||
);
|
||||
|
||||
$context = context_system::instance();
|
||||
@@ -712,6 +728,8 @@ class core_user_externallib_testcase extends externallib_advanced_testcase {
|
||||
$this->assertEquals($dbuser->description, $user1['description']);
|
||||
$this->assertEquals($dbuser->city, $user1['city']);
|
||||
$this->assertEquals($dbuser->country, $user1['country']);
|
||||
$this->assertEquals('atto', get_user_preferences('htmleditor', null, $dbuser));
|
||||
$this->assertEquals(null, get_user_preferences('invalidpreference', null, $dbuser));
|
||||
|
||||
// Call without required capability.
|
||||
$this->unassignUserCapability('moodle/user:update', $context->id, $roleid);
|
||||
|
||||
Reference in New Issue
Block a user