Commit f3182dd6 authored by Richard Mansfield's avatar Richard Mansfield
Browse files

Admin page to manage SafeIframe sites (bug #971282)



Adds a new page for Site admins to manage the list of sites for which
iframes are allowed by htmlpurifier.  Whenever an item is added,
edited, or deleted, the regex used by HTMLPurifier is updated.  Sites
are identified by favicon, and by a string entered by the Admin to be
used as the alt/title text for the favicon image.  The source of the
favicon image can be modified in config.php, but the google service is
used by default.

Change-Id: I4117de82691a002bf250ea71622eccfad4d5f8df
Signed-off-by: default avatarRichard Mansfield <richard.mansfield@catalyst.net.nz>
parent 4b8c5170
<?php
/**
* Mahara: Electronic portfolio, weblog, resume builder and social networking
* Copyright (C) 2011 Catalyst IT Ltd and others; see:
* http://wiki.mahara.org/Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package mahara
* @subpackage admin
* @author Richard Mansfield
*
*/
define('INTERNAL', 1);
define('ADMIN', 1);
define('MENUITEM', 'configextensions/iframesites');
define('SECTION_PLUGINTYPE', 'core');
define('SECTION_PLUGINNAME', 'admin');
define('SECTION_PAGE', 'iframesites');
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
require_once('pieforms/pieform.php');
require_once('upgrade.php');
define('TITLE', get_string('allowediframesites', 'admin'));
$iframesources = get_records_menu('iframe_source', '', '', 'name,prefix');
$iframedomains = get_records_menu('iframe_source_icon');
$newform = pieform(array(
'name' => 'newurl',
'elements' => array(
'url' => array(
'type' => 'text',
'title' => get_string('Site'),
'description' => get_string('iframeurldescription', 'admin'),
'rules' => array(
'minlength' => 4,
'maxlength' => 255,
'regex' => '/^[a-zA-Z0-9\/\._-]+$/',
),
),
'name' => array(
'type' => 'text',
'title' => get_string('displayname'),
'description' => get_string('iframedisplaynamedescription', 'admin'),
'rules' => array('minlength' => 1, 'maxlength' => 100),
),
'submit' => array(
'type' => 'submit',
'value' => get_string('add'),
),
),
));
$editurls = array();
$i = 0;
foreach ($iframesources as $url => $name) {
$elements = array(
'url' => array(
'type' => 'hidden',
'value' => $url,
),
'name' => array(
'type' => 'text',
'title' => get_string('displayname'),
'description' => get_string('iframedisplaynamedescription', 'admin'),
'defaultvalue' => $name,
),
'icon' => array(
'type' => 'text',
'title' => get_string('iframeiconhost', 'admin'),
'description' => get_string('iframeiconhostdescription', 'admin'),
'defaultvalue' => $iframedomains[$name],
'rules' => array(
'minlength' => 4,
'maxlength' => 253,
'regex' => '/^[a-zA-Z0-9-]{1,63}(\.[a-zA-Z0-9-]{1,63})+$/', // approximately
),
),
'submit' => array(
'type' => 'submit',
'value' => get_string('save'),
),
);
$editurls[$i] = array(
'id' => $i,
'url' => $url,
'name' => $name,
'icon' => favicon_display_url($iframedomains[$name]),
'editform' => pieform(array(
'name' => 'editurl_' . $i,
'successcallback' => 'editurl_submit',
'elements' => $elements,
)),
'deleteform' => pieform(array(
'name' => 'deleteurl_' . $i,
'successcallback' => 'deleteurl_submit',
'renderer' => 'oneline',
'class' => 'oneline inline',
'elements' => array(
'url' => array(
'type' => 'hidden',
'value' => $url,
),
'submit' => array(
'type' => 'image',
'src' => $THEME->get_url('images/icon_close.gif'),
'elementtitle' => get_string('delete'),
'confirm' => get_string('confirmdeletemenuitem', 'admin'),
),
),
)),
);
$i++;
}
function editurl_submit(Pieform $form, $values) {
global $iframesources, $iframedomains, $SESSION;
if (isset($iframesources[$values['url']])) {
$oldname = $iframesources[$values['url']];
$newname = strip_tags($values['name']);
$iframesources[$values['url']] = $newname;
db_begin();
// Update the icon list if necessary
if (!isset($iframedomains[$newname])) {
insert_record('iframe_source_icon', (object) array('name' => $newname, 'domain' => $values['icon']));
}
else if ($iframedomains[$newname] != $values['icon']) {
set_field('iframe_source_icon', 'domain', $values['icon'], 'name', $newname);
}
set_field('iframe_source', 'name', $newname, 'prefix', $values['url']);
if (!in_array($oldname, $iframesources)) {
delete_records('iframe_source_icon', 'name', $oldname);
}
update_safe_iframe_regex();
db_commit();
$SESSION->add_ok_msg(get_string('itemupdated'));
}
else {
$SESSION->add_error_msg(get_string('updatefailed'));
}
redirect('/admin/extensions/iframesites.php');
}
function deleteurl_submit(Pieform $form, $values) {
global $iframesources, $iframedomains, $SESSION;
if (isset($iframesources[$values['url']])) {
$oldname = $iframesources[$values['url']];
db_begin();
delete_records('iframe_source', 'prefix', $values['url']);
// If this was the last site using this name, remove it from
// the domain list too.
unset($iframesources[$values['url']]);
if (!in_array($oldname, $iframesources)) {
delete_records('iframe_source_icon', 'name', $oldname);
}
update_safe_iframe_regex();
db_commit();
$SESSION->add_ok_msg(get_string('itemdeleted'));
}
else {
$SESSION->add_error_msg(get_string('deletefailed', 'admin'));
}
redirect('/admin/extensions/iframesites.php');
}
function newurl_validate(Pieform $form, $values) {
global $iframesources;
if (!$urldata = process_allowed_iframe_url($values['url'])) {
$form->set_error('url', get_string('iframeinvalidsite', 'admin'));
}
if (isset($iframesources[$urldata['key']])) {
$form->set_error('url', get_string('urlalreadyexists'));
}
}
function newurl_submit(Pieform $form, $values) {
global $iframesources, $iframedomains;
$urldata = process_allowed_iframe_url($values['url']);
db_begin();
if (!isset($iframedomains[$values['name']])) {
insert_record('iframe_source_icon', (object) array('name' => $values['name'], 'domain' => strtolower($urldata['domain'])));
}
insert_record('iframe_source', (object) array('prefix' => $urldata['key'], 'name' => strip_tags($values['name'])));
update_safe_iframe_regex();
db_commit();
redirect('/admin/extensions/iframesites.php');
}
/**
* To generate the htmlpurifier URI.SafeIframeRegexp for allowed iframe
* sites, we'll only use the host and path parts of the given url, and
* just strip out the rest.
*
* @param string $url A URL prefix to be used in the htmlpurifier regex
*
* @return array with the following elements
* key: key for the iframesources array and to use in the regex,
* domain: domain to use when fetching a favicon for the site.
*/
function process_allowed_iframe_url($url) {
$parts = parse_url($url);
if (isset($parts['scheme'])) {
return false;
}
// Add 'http://' here just to run it through the url validator.
$tovalidate = 'http://' . $url;
if (!filter_var($tovalidate, FILTER_VALIDATE_URL)) {
return false;
}
$parts = parse_url($tovalidate);
if (isset($parts['host'])) {
$domain = $parts['host'];
$key = $parts['host'] . (isset($parts['path']) ? $parts['path'] : '');
}
else {
$domain = $key = $parts['path'];
}
// Add a trailing slash if there's no path part in the given site,
// to stop the user entering something obviously silly.
if (strpos($key, '/') === false) {
$key .= '/';
}
return array('domain' => $domain, 'key' => $key);
}
$js = <<<EOF
\$j(function() {
\$j('.url-open-editform').click(function(e) {
e.preventDefault();
\$j('#' + this.id + '-form').toggleClass('js-hidden');
});
});
EOF;
$smarty = smarty(
array('jquery'),
array(),
array(),
array('sidebars' => false)
);
$smarty->assign('PAGEHEADING', TITLE);
$smarty->assign('INLINEJAVASCRIPT', $js);
$smarty->assign('editurls', $editurls);
$smarty->assign('newform', $newform);
$smarty->display('admin/extensions/iframesites.tpl');
......@@ -146,6 +146,16 @@ $string['newfiltersdescription'] = 'If you have downloaded a new set of HTML fil
$string['filtersinstalled'] = 'Filters installed.';
$string['nofiltersinstalled'] = 'No HTML filters installed.';
$string['allowediframesites'] = 'Allowed iframe sources';
$string['allowediframesitesdescriptionshort'] = 'Configure permissions for embedding external iframe content';
$string['allowediframesitesdescription'] = 'Users are allowed to embed content from the following external sites on their pages, inside HTML &lt;iframe&gt; elements. Typically this is used to display videos hosted elsewhere. The list of allowed sites can be modified on this page.';
$string['allowediframesitesdescriptiondetail'] = 'The icon and display name will be visible to users when they configure an external media block. All sites with the same display name are grouped together in the configuration form, but iframe source text matching any of the sites will be allowed.';
$string['iframeurldescription'] = "Text to match at the beginning of the iframe source URL (without the http://). Only letters, digits, and the characters '.', '/', '_', and '-' are allowed.";
$string['iframedisplaynamedescription'] = 'The name of the site to be displayed to users.';
$string['iframeinvalidsite'] = "This field should contain a valid host, an optional path, and can contain only letters, digits, '.', '/', '_', and '-'.";
$string['iframeiconhost'] = 'Icon host';
$string['iframeiconhostdescription'] = 'If you wish, you may specify a different host for the favicon image. All sites with the same name will use this icon.';
// sanity check warnings
$string['warnings'] = 'Warning';
......
<h3>Allowed iframe sources</h3>
<p>The source of any &lt;iframe&gt; tags entered by users will be stripped out whenever a page is displayed, unless the beginning of the source URL matches one or more of the sites in this list.</p>
<p>To reduce the chance of any malicious content being served from an iframe on your site, you should try to make these strings as long as possible, while still matching the beginning of all the source URLs you wish to allow.</p>
<p>When you add a new site on this page, the site's favicon host is guessed from the text you enter, and this host is used for the icon. You can change the favicon once you have added a new site by clicking on the edit button.</p>
......@@ -383,6 +383,8 @@ $string['institutionfull'] = 'The institution you have chosen is not accepting a
$string['registrationnotallowed'] = 'The institution you have chosen does not allow self-registration.';
$string['registrationcomplete'] = 'Thank you for registering at %s';
$string['language'] = 'Language';
$string['itemdeleted'] = 'Item deleted';
$string['itemupdated'] = 'Item updated';
// Forgot password
$string['cantchangepassword'] = 'Sorry, you are unable to change your password through this interface - please use your institution\'s interface instead.';
......
......@@ -258,3 +258,11 @@ $cfg->sslproxy = false;
// If true, new copies of views & collections will have 'Copy of' prepended to the title.
$cfg->renamecopies = true;
// Favicon display: string used to get the favicon image src from a given domain.
// Used to display the sites whose iframe embed code is allowed by htmlpurifier.
// Either assume that favicon.ico exists at the root of the domain, or use a service.
// $cfg->favicondisplay = 'http://%s/favicon.ico';
$cfg->favicondisplay = 'http://www.google.com/s2/favicons?domain=%s';
// $cfg->favicondisplay = 'http://www.grabicon.com/%s';
// $cfg->favicondisplay = 'http://www.getfavicon.org/?url=%s';
......@@ -1944,6 +1944,12 @@ function admin_nav() {
'title' => get_string('htmlfilters', 'admin'),
'weight' => 20,
),
'configextensions/iframesites' => array(
'path' => 'configextensions/iframesites',
'url' => 'admin/extensions/iframesites.php',
'title' => get_string('allowediframesites', 'admin'),
'weight' => 30,
),
);
return $menu;
......@@ -3397,3 +3403,11 @@ function clean_email_headers($headertext) {
return substr($filtered, 0, 100);
}
function favicon_display_url($host) {
$url = sprintf(get_config('favicondisplay'), $host);
if (is_https()) {
$url = str_replace('http://', 'https://', $url);
}
return $url;
}
......@@ -763,3 +763,9 @@ form#uploadcsv {
font-weight: bold;
margin: 0 .25em 0 0;
}
.iframesources {
min-width: 75%;
}
.iframesources .editrow form td.description {
max-width: 500px;
}
{include file="header.tpl"}
<p>{str tag=allowediframesitesdescription section=admin}</p>
<p>{str tag=allowediframesitesdescriptiondetail section=admin}</p>
{if $editurls}
<table class="iframesources">
<thead>
<tr>
<th>{str tag=Site}</th>
<th>{str tag=displayname}</th>
<th></th>
</tr>
</thead>
<tbody>
{foreach from=$editurls item=item name=urls}
<tr class="{cycle values='r0,r1' advance=false}">
<td><strong>{$item.url}</strong></td>
<td><img src="{$item.icon}" alt="{$item.name}" title="{$item.name}">&nbsp;{$item.name}</td>
<td class="right buttonscell btns2">
<a id="edit-{$item.id}" class="url-open-editform nojs-hidden-inline" title="{str tag=edit}" href="">
<img src="{theme_url filename="images/edit.gif"}">
</a>
{$item.deleteform|safe}
</td>
</tr>
<tr class="editrow {cycle} url-editform js-hidden" id="edit-{$item.id}-form">
<td colspan=3>{$item.editform|safe}</td>
</tr>
{/foreach}
</tbody>
</table>
{/if}
<hr>
{$newform|safe}
{include file="footer.tpl"}
......@@ -117,6 +117,7 @@
<ul>
<li><strong><a href="{$WWWROOT}admin/extensions/plugins.php">{str tag=pluginadmin section=admin}</a></strong> - {str tag=pluginadmindescription section=admin}</li>
<li><strong><a href="{$WWWROOT}admin/extensions/filter.php">{str tag=htmlfilters section=admin}</a></strong> - {str tag=htmlfiltersdescription section=admin}</li>
<li><strong><a href="{$WWWROOT}admin/extensions/iframesites.php">{str tag=allowediframesites section=admin}</a></strong> - {str tag=allowediframesitesdescriptionshort section=admin}</li>
</ul>
</div>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment