trusttext:

* proposed by Martin Dougiamas
 * implemented by skodak

Usage:
1/ change enabletrusttext to yes in site settings (it is off by default) or set it in config.php
2/ assign moodle/site:trustcontent capability to users whose text submitted in glossary entries, comments, forum posts etc. should not be cleaned == they can use javascript or any other forbidden tags in glossary and forums...

done:
 * core
 * glossary (without proper upgrade)

to do:
 * data cleaning in upgrades
 * forum, blocks and some other places (MD decides)
This commit is contained in:
skodak 2006-08-26 13:00:07 +00:00
parent 8618b509fd
commit 7d8a3cb06a
12 changed files with 193 additions and 33 deletions

View file

@ -225,6 +225,9 @@ class configvarrss extends configvar {
$permissions['allowobjectembed'] = new configvar (get_string('configallowobjectembed', 'admin'), $permissions['allowobjectembed'] = new configvar (get_string('configallowobjectembed', 'admin'),
choose_from_menu ($noyesoptions, 'allowobjectembed', $config->allowobjectembed, '', '', '', true) ); choose_from_menu ($noyesoptions, 'allowobjectembed', $config->allowobjectembed, '', '', '', true) );
/// enabletrusttext
$permissions['enabletrusttext'] = new configvar (get_string('configenabletrusttext', 'admin'),
choose_from_menu ($noyesoptions, 'enabletrusttext', $config->enabletrusttext, '', '', '', true) );
unset($options); unset($options);
$options['none'] = 'No courses'; $options['none'] = 'No courses';

View file

@ -77,6 +77,7 @@ $temp->add(new admin_setting_configcheckbox('opentogoogle', get_string('opentogo
$temp->add(new admin_setting_configtext('maxbytes', get_string('maxbytes', 'admin'), get_string('configmaxbytes', 'admin'), PARAM_INT)); $temp->add(new admin_setting_configtext('maxbytes', get_string('maxbytes', 'admin'), get_string('configmaxbytes', 'admin'), PARAM_INT));
$temp->add(new admin_setting_configcheckbox('messaging', get_string('messaging', 'admin'), get_string('configmessaging','admin'))); $temp->add(new admin_setting_configcheckbox('messaging', get_string('messaging', 'admin'), get_string('configmessaging','admin')));
$temp->add(new admin_setting_configcheckbox('allowobjectembed', get_string('allowobjectembed', 'admin'), get_string('configallowobjectembed', 'admin'))); $temp->add(new admin_setting_configcheckbox('allowobjectembed', get_string('allowobjectembed', 'admin'), get_string('configallowobjectembed', 'admin')));
$temp->add(new admin_setting_configcheckbox('enabletrusttext', get_string('enabletrusttext', 'admin'), get_string('configenabletrusttext', 'admin')));
$temp->add(new admin_setting_configselect('maxeditingtime', get_string('maxeditingtime','admin'), get_string('configmaxeditingtime','admin'), array(60 => get_string('numminutes', '', 1), $temp->add(new admin_setting_configselect('maxeditingtime', get_string('maxeditingtime','admin'), get_string('configmaxeditingtime','admin'), array(60 => get_string('numminutes', '', 1),
300 => get_string('numminutes', '', 5), 300 => get_string('numminutes', '', 5),
900 => get_string('numminutes', '', 15), 900 => get_string('numminutes', '', 15),

View file

@ -186,6 +186,20 @@ $moodle_capabilities = array(
) )
), ),
'moodle/site:trustcontent' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM,
'legacy' => array(
'guest' => CAP_PREVENT,
'student' => CAP_PREVENT,
'teacher' => CAP_PREVENT,
'editingteacher' => CAP_ALLOW,
'coursecreator' => CAP_ALLOW,
'admin' => CAP_ALLOW
)
),
'moodle/user:create' => array( 'moodle/user:create' => array(
'captype' => 'write', 'captype' => 'write',

View file

@ -34,6 +34,7 @@
'enablecourserequests' => 0, 'enablecourserequests' => 0,
'enablerssfeeds' => 0, 'enablerssfeeds' => 0,
'enablestats' => 0, 'enablestats' => 0,
'enabletrusttext' => 0,
'enrol' => 'internal', 'enrol' => 'internal',
'extendedusernamechars' => false, 'extendedusernamechars' => false,
'editorbackgroundcolor' => '#ffffff', 'editorbackgroundcolor' => '#ffffff',

View file

@ -72,6 +72,11 @@ define('FORMAT_WIKI', '3'); // Wiki-formatted text
*/ */
define('FORMAT_MARKDOWN', '4'); // Markdown-formatted text http://daringfireball.net/projects/markdown/ define('FORMAT_MARKDOWN', '4'); // Markdown-formatted text http://daringfireball.net/projects/markdown/
/**
* TRUSTTEXT marker - if present in text, text cleaning should be bypassed
*/
define('TRUSTTEXT', '#####TRUSTTEXT#####');
/** /**
* Allowed tags - string of html tags that can be tested against for safe html tags * Allowed tags - string of html tags that can be tested against for safe html tags
@ -1234,10 +1239,14 @@ function format_text_menu() {
* @return string * @return string
* @todo Finish documenting this function * @todo Finish documenting this function
*/ */
function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL ) { function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL) {
global $CFG, $course; global $CFG, $course;
if (!isset($options->trusttext)) {
$options->trusttext = false;
}
if (!isset($options->noclean)) { if (!isset($options->noclean)) {
$options->noclean=false; $options->noclean=false;
} }
@ -1262,7 +1271,7 @@ function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL
if (!empty($CFG->cachetext)) { if (!empty($CFG->cachetext)) {
$time = time() - $CFG->cachetext; $time = time() - $CFG->cachetext;
$md5key = md5($text.'-'.$courseid.$options->noclean.$options->smiley.$options->filter.$options->para.$options->newlines.$format.current_language().$courseid); $md5key = md5($text.'-'.$courseid.$options->noclean.$options->smiley.$options->filter.$options->para.$options->newlines.$format.current_language().$courseid.$options->trusttext);
if ($oldcacheitem = get_record_sql('SELECT * FROM '.$CFG->prefix.'cache_text WHERE md5key = \''.$md5key.'\'', true)) { if ($oldcacheitem = get_record_sql('SELECT * FROM '.$CFG->prefix.'cache_text WHERE md5key = \''.$md5key.'\'', true)) {
if ($oldcacheitem->timemodified >= $time) { if ($oldcacheitem->timemodified >= $time) {
return $oldcacheitem->formattedtext; return $oldcacheitem->formattedtext;
@ -1270,17 +1279,31 @@ function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL
} }
} }
// trusttext overrides the noclean option!
if ($options->trusttext) {
if (trusttext_present($text)) {
$text = trusttext_strip($text);
if (!empty($CFG->enabletrusttext)) {
$options->noclean = true;
} else {
$options->noclean = false;
}
} else {
$options->noclean = false;
}
}
$CFG->currenttextiscacheable = true; // Default status - can be changed by any filter $CFG->currenttextiscacheable = true; // Default status - can be changed by any filter
switch ($format) { switch ($format) {
case FORMAT_HTML: case FORMAT_HTML:
if (!empty($options->smiley)) { if ($options->smiley) {
replace_smilies($text); replace_smilies($text);
} }
if (empty($options->noclean)) { if (!$options->noclean) {
$text = clean_text($text, $format); $text = clean_text($text, $format);
} }
if (!empty($options->filter)) { if ($options->filter) {
$text = filter_text($text, $courseid); $text = filter_text($text, $courseid);
} }
break; break;
@ -1302,25 +1325,25 @@ function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL
case FORMAT_MARKDOWN: case FORMAT_MARKDOWN:
$text = markdown_to_html($text); $text = markdown_to_html($text);
if (!empty($options->smiley)) { if ($options->smiley) {
replace_smilies($text); replace_smilies($text);
} }
if (empty($options->noclean)) { if (!$options->noclean) {
$text = clean_text($text, $format); $text = clean_text($text, $format);
} }
if (!empty($options->filter)) { if ($options->filter) {
$text = filter_text($text, $courseid); $text = filter_text($text, $courseid);
} }
break; break;
default: // FORMAT_MOODLE or anything else default: // FORMAT_MOODLE or anything else
$text = text_to_html($text, $options->smiley, $options->para, $options->newlines); $text = text_to_html($text, $options->smiley, $options->para, $options->newlines);
if (empty($options->noclean)) { if (!$options->noclean) {
$text = clean_text($text, $format); $text = clean_text($text, $format);
} }
if (!empty($options->filter)) { if ($options->filter) {
$text = filter_text($text, $courseid); $text = filter_text($text, $courseid);
} }
break; break;
@ -1475,7 +1498,6 @@ function format_text_email($text, $format) {
* @todo Finish documenting this function * @todo Finish documenting this function
*/ */
function filter_text($text, $courseid=NULL) { function filter_text($text, $courseid=NULL) {
global $CFG; global $CFG;
require_once($CFG->libdir.'/filterlib.php'); require_once($CFG->libdir.'/filterlib.php');
@ -1499,6 +1521,88 @@ function filter_text($text, $courseid=NULL) {
return $text; return $text;
} }
/**
* Is the text marked as trusted?
*
* @param string $text text to be searched for TRUSTTEXT marker
* @return boolean
*/
function trusttext_present($text) {
if (strpos($text, TRUSTTEXT) !== FALSE) {
return true;
} else {
return false;
}
}
/**
* This funtion MUST be called before the cleaning or any other
* function that modifies the data! We do not know the origin of trusttext
* in database, if it gets there in tweaked form we must not convert it
* to supported form!!!
*
* Please be carefull not to use stripslashes on data from database
* or twice stripslashes when processing data recieved from user.
*
* @param string $text text that may contain TRUSTTEXT marker
* @return text without any TRUSTTEXT marker
*/
function trusttext_strip($text) {
global $CFG;
while (true) { //removing nested TRUSTTEXT
$orig = $text;
$text = str_replace(TRUSTTEXT, '', $text);
if (strcmp($orig, $text) === 0) {
return $text;
}
}
}
/**
* Mark text as trusted, such text may contain any HTML tags because the
* normal text cleaning will be bypassed.
* Please make sure that the text comes from trusted user before storing
* it into database!
*/
function trusttext_mark($text) {
global $CFG;
if (!empty($CFG->enabletrusttext) and (strpos($text, TRUSTTEXT) === FALSE)) {
return TRUSTTEXT.$text;
} else {
return $text;
}
}
function trusttext_after_edit(&$text, $context) {
if (has_capability('moodle/site:trustcontent', $context)) {
$text = trusttext_mark($text);
} else {
$text = trusttext_strip($text);
}
}
function trusttext_prepare_edit(&$text, &$format, $usehtmleditor, $context) {
global $CFG;
$options = new object();
$options->smiley = false;
$options->filter = false;
if (!empty($CFG->enabletrusttext)
and has_capability('moodle/site:trustcontent', $context)
and trusttext_present($text)) {
$options->noclean = true;
} else {
$options->noclean = false;
}
$text = trusttext_strip($text);
if ($usehtmleditor) {
$text = format_text($text, $format, $options);
$format = FORMAT_HTML;
} else if (!$options->noclean){
$text = clean_text($text, $format);
}
}
/** /**
* Given raw text (eg typed in by a user), this function cleans it up * Given raw text (eg typed in by a user), this function cleans it up
* and removes any nasty tags that could mess up Moodle pages. * and removes any nasty tags that could mess up Moodle pages.

View file

@ -2,13 +2,9 @@
if (!isset($form->format)) { if (!isset($form->format)) {
$form->format = $defaultformat; $form->format = $defaultformat;
} }
if ($usehtmleditor) { //clean and convert before editing
$options = new object(); trusttext_prepare_edit($form->text, $form->format, $usehtmleditor, $context)
$options->smiley = false;
$options->filter = false;
$form->text = format_text($form->text, $form->format, $options);
$form->format = FORMAT_HTML;
}
?> ?>
<form name="form" method="post" action="comment.php"> <form name="form" method="post" action="comment.php">
<table class="generalbox"> <table class="generalbox">

View file

@ -134,7 +134,7 @@
} }
if ( $confirm and $form = data_submitted() ) { if ( $confirm and $form = data_submitted() ) {
//$form->text = clean_text($form->text, $form->format); trusttext_after_edit($form->text, $context);
$newentry->entryid = $entry->id; $newentry->entryid = $entry->id;
$newentry->comment = $form->text; $newentry->comment = $form->text;

View file

@ -2,13 +2,9 @@
if (!isset($newentry->format)) { if (!isset($newentry->format)) {
$newentry->format = $defaultformat; $newentry->format = $defaultformat;
} }
if ($usehtmleditor) { //clean and convert before editing
$options = new object(); trusttext_prepare_edit($newentry->definition, $newentry->format, $usehtmleditor, $context)
$options->smiley = false;
$options->filter = false;
$newentry->definition = format_text($newentry->definition, $newentry->format, $options);
$newentry->format = FORMAT_HTML;
}
?> ?>
<form name="form" method="post" action="edit.php" enctype="multipart/form-data"> <form name="form" method="post" action="edit.php" enctype="multipart/form-data">
<table border="0" cellpadding="5"> <table border="0" cellpadding="5">

View file

@ -43,6 +43,8 @@ if (!$glossary->studentcanpost && !has_capability('mod/glossary:manageentries',
} }
if ( $confirm ) { if ( $confirm ) {
$form = data_submitted(); $form = data_submitted();
trusttext_after_edit($form->text, $context);
if ( !isset($form->usedynalink) ) { if ( !isset($form->usedynalink) ) {
$form->usedynalink = 0; $form->usedynalink = 0;
} }
@ -245,6 +247,7 @@ if ( $confirm ) {
$newentry->userid = $form->userid; $newentry->userid = $form->userid;
$newentry->timecreated = $form->timecreated; $newentry->timecreated = $form->timecreated;
if ( $aliases = get_records("glossary_alias","entryid",$e) ) { if ( $aliases = get_records("glossary_alias","entryid",$e) ) {
foreach ($aliases as $alias) { foreach ($aliases as $alias) {
$newentry->aliases .= $alias->alias . "\n"; $newentry->aliases .= $alias->alias . "\n";
@ -332,7 +335,7 @@ $tab = GLOSSARY_ADDENTRY_VIEW;
include("tabs.html"); include("tabs.html");
if (!$e) { if (!$e) {
require_capability('glossary_write', $context); require_capability('mod/glossary:write', $context);
} }
include("edit.html"); include("edit.html");

View file

@ -212,7 +212,7 @@
$xmlentry = $xmlentries[$i]; $xmlentry = $xmlentries[$i];
unset($newentry); unset($newentry);
$newentry->concept = trim(addslashes($xmlentry['#']['CONCEPT'][0]['#'])); $newentry->concept = trim(addslashes($xmlentry['#']['CONCEPT'][0]['#']));
$newentry->definition = addslashes($xmlentry['#']['DEFINITION'][0]['#']); $newentry->definition = trusttext_strip(addslashes($xmlentry['#']['DEFINITION'][0]['#']));
if ( isset($xmlentry['#']['CASESENSITIVE'][0]['#']) ) { if ( isset($xmlentry['#']['CASESENSITIVE'][0]['#']) ) {
$newentry->casesensitive = addslashes($xmlentry['#']['CASESENSITIVE'][0]['#']); $newentry->casesensitive = addslashes($xmlentry['#']['CASESENSITIVE'][0]['#']);
} else { } else {

View file

@ -588,13 +588,36 @@ function glossary_print_entry($course, $cm, $glossary, $entry, $mode='',$hook=''
//Default (old) print format used if custom function doesn't exist in format //Default (old) print format used if custom function doesn't exist in format
function glossary_print_entry_default ($entry) { function glossary_print_entry_default ($entry) {
echo '<b>'. strip_tags($entry->concept) . ': </b>'; echo '<b>'. strip_tags($entry->concept) . ': </b>';
$definition = $entry->definition;
// always detect and strip TRUSTTEXT marker before processing and add+strip it afterwards!
if (trusttext_present($definition)) {
$ttpresent = true;
$definition = trusttext_strip($definition);
} else {
$ttpresent = false;
}
$definition = '<span class="nolink">' . strip_tags($definition) . '</span>';
// reconstruct the TRUSTTEXT properly after processing
if ($ttpresent) {
$definition = trusttext_mark($definition);
} else {
$definition = trusttext_strip($definition); //make 100% sure TRUSTTEXT marker was not created
}
$options = new object();
$options->para = false; $options->para = false;
$definition = format_text('<span class="nolink">' . strip_tags($entry->definition) . '</span>', $entry->format,$options); $options->trusttext = true;
$definition = format_text($definition, $entry->format, $options);
echo ($definition); echo ($definition);
echo '<br /><br />'; echo '<br /><br />';
} }
function glossary_print_entry_concept($entry) { function glossary_print_entry_concept($entry) {
$options = new object();
$options->para = false; $options->para = false;
$text = format_text('<span class="nolink">' . $entry->concept . '</span>', FORMAT_MOODLE, $options); $text = format_text('<span class="nolink">' . $entry->concept . '</span>', FORMAT_MOODLE, $options);
if (!empty($entry->highlight)) { if (!empty($entry->highlight)) {
@ -607,6 +630,14 @@ function glossary_print_entry_definition($entry) {
$definition = $entry->definition; $definition = $entry->definition;
// always detect and strip TRUSTTEXT marker before processing and add+strip it afterwards!
if (trusttext_present($definition)) {
$ttpresent = true;
$definition = trusttext_strip($definition);
} else {
$ttpresent = false;
}
$links = array(); $links = array();
$tags = array(); $tags = array();
$urls = array(); $urls = array();
@ -702,9 +733,18 @@ function glossary_print_entry_definition($entry) {
$definition = str_replace(array_keys($links),$links,$definition); $definition = str_replace(array_keys($links),$links,$definition);
} }
$options = new object();
$options->para = false; $options->para = false;
$options->trusttext = true;
$text = format_text($definition, $entry->format,$options); // reconstruct the TRUSTTEXT properly after processing
if ($ttpresent) {
$definition = trusttext_mark($definition);
} else {
$definition = trusttext_strip($definition); //make 100% sure TRUSTTEXT marker was not created
}
$text = format_text($definition, $entry->format, $options);
if (!empty($entry->highlight)) { if (!empty($entry->highlight)) {
$text = highlight($entry->highlight, $text); $text = highlight($entry->highlight, $text);
} }
@ -1537,7 +1577,9 @@ function glossary_print_comment($course, $cm, $glossary, $entry, $comment) {
echo '&nbsp;'; echo '&nbsp;';
echo '</td><td class="entry">'; echo '</td><td class="entry">';
echo format_text($comment->comment, $comment->format); $options = new object();
$options->trusttext = true;
echo format_text($comment->comment, $comment->format, $options);
echo '<div class="icons commands">'; echo '<div class="icons commands">';
@ -1692,7 +1734,7 @@ function glossary_generate_export_file($glossary, $hook = "", $hook = 0) {
if ( $entry->approved and $permissiongranted ) { if ( $entry->approved and $permissiongranted ) {
$co .= glossary_start_tag("ENTRY",3,true); $co .= glossary_start_tag("ENTRY",3,true);
$co .= glossary_full_tag("CONCEPT",4,false,trim($entry->concept)); $co .= glossary_full_tag("CONCEPT",4,false,trim($entry->concept));
$co .= glossary_full_tag("DEFINITION",4,false,$entry->definition); $co .= glossary_full_tag("DEFINITION",4,false,trusttext_strip($entry->definition));
$co .= glossary_full_tag("FORMAT",4,false,$entry->format); $co .= glossary_full_tag("FORMAT",4,false,$entry->format);
$co .= glossary_full_tag("USEDYNALINK",4,false,$entry->usedynalink); $co .= glossary_full_tag("USEDYNALINK",4,false,$entry->usedynalink);
$co .= glossary_full_tag("CASESENSITIVE",4,false,$entry->casesensitive); $co .= glossary_full_tag("CASESENSITIVE",4,false,$entry->casesensitive);

View file

@ -6,7 +6,7 @@
// This is compared against the values stored in the database to determine // This is compared against the values stored in the database to determine
// whether upgrades should be performed (see lib/db/*.php) // whether upgrades should be performed (see lib/db/*.php)
$version = 2006082300; // YYYYMMDD = date $version = 2006082600; // YYYYMMDD = date
// XY = increments within a single day // XY = increments within a single day
$release = '1.7 dev'; // Human-friendly version name $release = '1.7 dev'; // Human-friendly version name