Skip to content

Commit

Permalink
MDL-48498 admin: new setting types for curl host/port restrictions
Browse files Browse the repository at this point in the history
Two new classes in lib/adminlib. One providing validation for a
newline-delimited textarea supporting domain names, domain wildcards,
IPv4/IPv6 addresses and IPv4/IPv6 ranges. The second providing
validation for a newline-delimited textarea of port numbers.
  • Loading branch information
snake committed Nov 8, 2016
1 parent d72c977 commit 0672689
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 2 deletions.
9 changes: 8 additions & 1 deletion admin/settings/security.php
Expand Up @@ -118,8 +118,15 @@
$temp->add(new admin_setting_configcheckbox('cookiehttponly', new lang_string('cookiehttponly', 'admin'), new lang_string('configcookiehttponly', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowframembedding', new lang_string('allowframembedding', 'admin'), new lang_string('allowframembedding_help', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('loginpasswordautocomplete', new lang_string('loginpasswordautocomplete', 'admin'), new lang_string('loginpasswordautocomplete_help', 'admin'), 0));
$ADMIN->add('security', $temp);

// Settings elements used by the \core\files\curl_security_helper class.
$temp->add(new admin_setting_configmixedhostiplist('curlsecurityblockedhosts',
new lang_string('curlsecurityblockedhosts', 'admin'),
new lang_string('curlsecurityblockedhostssyntax', 'admin'), ""));
$temp->add(new admin_setting_configportlist('curlsecurityallowedport',
new lang_string('curlsecurityallowedport', 'admin'),
new lang_string('curlsecurityallowedportsyntax', 'admin'), ""));
$ADMIN->add('security', $temp);

// "notifications" settingpage
$temp = new admin_settingpage('notifications', new lang_string('notifications', 'admin'));
Expand Down
7 changes: 7 additions & 0 deletions lang/en/admin.php
Expand Up @@ -394,6 +394,11 @@
$string['cronwarning'] = 'The <a href="{$a}">cron.php maintenance script</a> has not been run for at least 24 hours.';
$string['cronwarningcli'] = 'The cli/cron.php maintenance script has not been run for at least 24 hours.';
$string['ctyperequired'] = 'The ctype PHP extension is now required by Moodle, in order to improve site performance and to offer multilingual compatibility.';
$string['curlsecurityallowedport'] = 'cURL allowed ports list';
$string['curlsecurityallowedportsyntax'] = 'Put every entry on one line. Valid entries are integer numbers only.';
$string['curlsecurityblockedhosts'] = 'cURL blocked hosts list';
$string['curlsecurityblockedhostssyntax'] = 'Put each entry on a new line. Valid entries are either full IPv4 or IPv6 addresses (such as <b>192.168.10.1, 0:0:0:0:0:0:0:1, ::1, fe80::</b>) which match a single host; or CIDR notation (such as <b>231.54.211.0/20 or fe80::/64</b>); or a range of IP addresses (such as <b>231.3.56.10-20 or fe80::1111-bbbb</b>) where the range applies to the last group of the address; or domain names (such as <b>localhost or example.com</b>); or wildcard domain names (such as <b>*.example.com or *.sub.example.com</b>). Blank lines are not allowed.';
$string['curlsecurityurlblocked'] = 'The URL is blocked.';
$string['curlcache'] = 'cURL cache TTL';
$string['curlrequired'] = 'The cURL PHP extension is now required by Moodle, in order to communicate with Moodle repositories.';
$string['curltimeoutkbitrate'] = 'Bitrate to use when calculating cURL timeouts (Kbps)';
Expand Down Expand Up @@ -1213,7 +1218,9 @@
$string['userquota'] = 'User quota';
$string['usesitenameforsitepages'] = 'Use site name for site pages';
$string['usetags'] = 'Enable tags functionality';
$string['validateemptylineerror'] = 'Empty lines are not valid';
$string['validateerror'] = 'This value is not valid';
$string['validateerrorlist'] = 'These entries are invalid: {$a}';
$string['validateiperror'] = 'These IP addresses are invalid: {$a}';
$string['verifychangedemail'] = 'Restrict domains when changing email';
$string['warningcurrentsetting'] = 'Invalid current value: {$a}';
Expand Down
173 changes: 173 additions & 0 deletions lib/adminlib.php
Expand Up @@ -3583,6 +3583,179 @@ public function validate($data) {
}
}

/**
* Used to validate a textarea used for domain names, wildcard domain names and IP addresses/ranges (both IPv4 and IPv6 format).
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
*/
class admin_setting_configmixedhostiplist extends admin_setting_configtextarea {

/**
* Validate the contents of the textarea as either IP addresses, domain name or wildcard domain name (RFC 4592).
* Used to validate a new line separated list of entries collected from a textarea control.
*
* This setting provides support for internationalised domain names (IDNs), however, such UTF-8 names will be converted to
* their ascii-compatible encoding (punycode) on save, and converted back to their UTF-8 representation when fetched
* via the get_setting() method, which has been overriden.
*
* @param string $data A list of FQDNs, DNS wildcard format domains, and IP addresses, separated by new lines.
* @return mixed bool true for success or string:error on failure
*/
public function validate($data) {
if (empty($data)) {
return true;
}
$entries = explode("\n", $data);
$badentries = [];

foreach ($entries as $key => $entry) {
$entry = trim($entry);
if (empty($entry)) {
return get_string('validateemptylineerror', 'admin');
}

// Validate each string entry against the supported formats.
if (\core\ip_utils::is_ip_address($entry) || \core\ip_utils::is_ipv6_range($entry)
|| \core\ip_utils::is_ipv4_range($entry) || \core\ip_utils::is_domain_name($entry)
|| \core\ip_utils::is_domain_matching_pattern($entry)) {
continue;
}

// Otherwise, the entry is invalid.
$badentries[] = $entry;
}

if ($badentries) {
return get_string('validateerrorlist', 'admin', join(', ', $badentries));
}
return true;
}

/**
* Convert any lines containing international domain names (IDNs) to their ascii-compatible encoding (ACE).
*
* @param string $data the setting data, as sent from the web form.
* @return string $data the setting data, with all IDNs converted (using punycode) to their ascii encoded version.
*/
protected function ace_encode($data) {
if (empty($data)) {
return $data;
}
$entries = explode("\n", $data);
foreach ($entries as $key => $entry) {
$entry = trim($entry);
// This regex matches any string which:
// a) contains at least one non-ascii unicode character AND
// b) starts with a-zA-Z0-9 or any non-ascii unicode character AND
// c) ends with a-zA-Z0-9 or any non-ascii unicode character
// d) contains a-zA-Z0-9, hyphen, dot or any non-ascii unicode characters in the middle.
if (preg_match('/^(?=[^\x00-\x7f])([^\x00-\x7f]|[a-zA-Z0-9])([^\x00-\x7f]|[a-zA-Z0-9-.])*([^\x00-\x7f]|[a-zA-Z0-9])$/',
$entry)) {
// If we can convert the unicode string to an idn, do so.
// Otherwise, leave the original unicode string alone and let the validation function handle it (it will fail).
$val = idn_to_ascii($entry);
$entries[$key] = $val ? $val : $entry;
}
}
return implode("\n", $entries);
}

/**
* Decode any ascii-encoded domain names back to their utf-8 representation for display.
*
* @param string $data the setting data, as found in the database.
* @return string $data the setting data, with all ascii-encoded IDNs decoded back to their utf-8 representation.
*/
protected function ace_decode($data) {
$entries = explode("\n", $data);
foreach ($entries as $key => $entry) {
$entry = trim($entry);
if (strpos($entry, 'xn--') !== false) {
$entries[$key] = idn_to_utf8($entry);
}
}
return implode("\n", $entries);
}

/**
* Override, providing utf8-decoding for ascii-encoded IDN strings.
*
* @return string the setting string, with any punycode strings converted back to their utf-8 string representation.
*/
public function get_setting() {
// Here, we need to decode any ascii-encoded IDNs back to their native, utf-8 representation.
$data = $this->config_read($this->name);
if (function_exists('idn_to_utf8')) {
$data = $this->ace_decode($data);
}
return $data;
}

/**
* Override, providing ascii-encoding for utf8 (native) IDN strings.
*
* @param string $data
* @return string
*/
public function write_setting($data) {
if ($this->paramtype === PARAM_INT and $data === '') {
// Do not complain if '' used instead of 0.
$data = 0;
}

// Try to convert any non-ascii domains to ACE prior to validation - we can't modify anything in validate!
if (function_exists('idn_to_ascii')) {
$data = $this->ace_encode($data);
}

$validated = $this->validate($data);
if ($validated !== true) {
return $validated;
}
return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
}
}

/**
* Used to validate a textarea used for port numbers.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
*/
class admin_setting_configportlist extends admin_setting_configtextarea {

/**
* Validate the contents of the textarea as port numbers.
* Used to validate a new line separated list of ports collected from a textarea control.
*
* @param string $data A list of ports separated by new lines
* @return mixed bool true for success or string:error on failure
*/
public function validate($data) {
if (empty($data)) {
return true;
}
$ports = explode("\n", $data);
$badentries = [];
foreach ($ports as $port) {
$port = trim($port);
if (empty($port)) {
return get_string('validateemptylineerror', 'admin');
}

// Is the string a valid integer number?
if (strval(intval($port)) !== $port || intval($port) <= 0) {
$badentries[] = $port;
}
}
if ($badentries) {
return get_string('validateerrorlist', 'admin', $badentries);
}
return true;
}
}


/**
* An admin setting for selecting one or more users who have a capability
Expand Down
2 changes: 1 addition & 1 deletion version.php
Expand Up @@ -29,7 +29,7 @@

defined('MOODLE_INTERNAL') || die();

$version = 2016110600.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2016110600.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.

Expand Down

0 comments on commit 0672689

Please sign in to comment.