Skip to content

Commit

Permalink
Merge branch 'master' of github.com:catalyst/moodle-local_csp
Browse files Browse the repository at this point in the history
  • Loading branch information
brendanheywood committed Oct 2, 2019
2 parents 5d6b225 + 778ca66 commit 834589c
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 65 deletions.
48 changes: 29 additions & 19 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,59 @@ language: php

sudo: false

addons:
postgresql: "9.6"

services:
- mysql

cache:
directories:
- $HOME/.composer/cache

php:
- 5.6
- 7.1

env:
- DB=pgsql MOODLE_BRANCH=MOODLE_26_STABLE
- DB=pgsql MOODLE_BRANCH=MOODLE_27_STABLE
- DB=pgsql MOODLE_BRANCH=MOODLE_28_STABLE
- DB=pgsql MOODLE_BRANCH=MOODLE_29_STABLE
- DB=pgsql MOODLE_BRANCH=MOODLE_30_STABLE
- DB=pgsql MOODLE_BRANCH=MOODLE_31_STABLE
- DB=pgsql MOODLE_BRANCH=MOODLE_32_STABLE
- DB=pgsql MOODLE_BRANCH=MOODLE_33_STABLE
- DB=pgsql MOODLE_BRANCH=MOODLE_34_STABLE
- DB=pgsql MOODLE_BRANCH=MOODLE_35_STABLE
- DB=pgsql MOODLE_BRANCH=MOODLE_36_STABLE
- DB=pgsql MOODLE_BRANCH=MOODLE_37_STABLE
- DB=pgsql MOODLE_BRANCH=master
- DB=mysqli MOODLE_BRANCH=MOODLE_26_STABLE
- DB=mysqli MOODLE_BRANCH=MOODLE_27_STABLE
- DB=mysqli MOODLE_BRANCH=MOODLE_28_STABLE
- DB=mysqli MOODLE_BRANCH=MOODLE_29_STABLE
- DB=mysqli MOODLE_BRANCH=MOODLE_30_STABLE
- DB=mysqli MOODLE_BRANCH=MOODLE_31_STABLE
- DB=mysqli MOODLE_BRANCH=MOODLE_32_STABLE
- DB=mysqli MOODLE_BRANCH=MOODLE_33_STABLE
- DB=mysqli MOODLE_BRANCH=MOODLE_34_STABLE
- DB=mysqli MOODLE_BRANCH=MOODLE_35_STABLE
- DB=mysqli MOODLE_BRANCH=MOODLE_36_STABLE
- DB=mysqli MOODLE_BRANCH=MOODLE_37_STABLE
- DB=mysqli MOODLE_BRANCH=master

matrix:
include:
- php: 7.0
- php: 5.5
dist: trusty
env: DB=pgsql MOODLE_BRANCH=MOODLE_30_STABLE
- php: 7.0
- php: 5.5
dist: trusty
env: DB=pgsql MOODLE_BRANCH=MOODLE_31_STABLE
- php: 7.0
- php: 5.6
dist: trusty
env: DB=pgsql MOODLE_BRANCH=MOODLE_32_STABLE
- php: 7.0
env: DB=pgsql MOODLE_BRANCH=master
- php: 7.0
- php: 5.5
dist: trusty
env: DB=mysqli MOODLE_BRANCH=MOODLE_30_STABLE
- php: 7.0
- php: 5.5
dist: trusty
env: DB=mysqli MOODLE_BRANCH=MOODLE_31_STABLE
- php: 7.0
- php: 5.6
dist: trusty
env: DB=mysqli MOODLE_BRANCH=MOODLE_32_STABLE
- php: 7.0
env: DB=mysqli MOODLE_BRANCH=master

before_install:
- cd ../..
Expand Down
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Travis integration: [![Build Status](https://travis-ci.org/catalyst/moodle-local_csp.svg?branch=master)](https://travis-ci.org/catalyst/moodle-local_csp)
[![Build Status](https://travis-ci.org/catalyst/moodle-local_csp.svg?branch=master)](https://travis-ci.org/catalyst/moodle-local_csp)
# moodle-local_csp

* [Why would you want this?](#why-would-you-want-this)
Expand All @@ -11,26 +11,36 @@ Why would you want this?
------------------------
Security, security, security.

This plugin helps you to detect and eliminate security errors in your Moodle such as:
This plugin helps you to detect and mitigate certain classes of security errors in your Moodle such as:

- Mixed content (https/http) after you switched to HTTPS.
- Same origin (or specified origin) policy for scripts and media data.
- Same origin (or specified origin) policy for scripts and media data.
- Unintended iframes

What is this?
-------------
This plugin enables [Custom Security Policy headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) across the Moodle website.
This plugin allows you to easily test and rollout [Custom Security Policy headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) across your moodle.

Examples:
- Report/enforce SSL origin for links, images etc.
- Report/enforce same-origin for links, images etc.

How does it work?
-----------------
Site admin configures CSP headers: Content-Security-Policy or Content-Security-Policy-Report-Only in the plugin settings.

Site admin configures CSP headers: `Content-Security-Policy` or `Content-Security-Policy-Report-Only` in the plugin settings.
Header Content-Security-Policy-Report-Only is for recording CSP violations in Moodle and reviewing them later from the plugin's report page.

Enabling of Content-Security-Policy blocks browser from showing site resources that violate defined rules.

CSP support in browsers is quite good:

https://caniuse.com/#search=CSP

Installation
------------
Checkout or download the plugin source code into folder `local\csp` of your Moodle installation.

```sh
git clone [email protected]:catalyst/moodle-local_csp.git local\csp
```
Expand All @@ -42,19 +52,22 @@ unzip master.zip -d local/csp
```
Then go to your Moodle admin interface and complete installation and configuration.
Example policy 'default-src https:;' will be reporting or enforcing the links to be HTTPS-only. Please note, the whole moodle website should be accessible via HTTPS for this to work.

For more examples of other CSP directives please read [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP).

References
----------
Relevant issue in Moodle tracker: (https://tracker.moodle.org/browse/MDL-46269)

A complementary plugin which works by searching the moodle DB for bad links:
See also:

Convert http embedded content to https on https sites where available
https://tracker.moodle.org/browse/MDL-46269

A complementary plugin which works by searching the moodle DB for bad links:
https://github.com/moodlerooms/moodle-tool_httpsreplace


This plugin was developed by Catalyst IT Australia:

https://www.catalyst-au.net/

<img alt="Catalyst IT" src="https://cdn.rawgit.com/CatalystIT-AU/moodle-auth_saml2/master/pix/catalyst-logo.svg" width="400">
88 changes: 76 additions & 12 deletions classes/table/csp_report.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,30 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class csp_report extends \table_sql {

/**
* Embeds a link to a drilldown table showing only 1 violation class
*
* @param stdObject $record fieldset object of db table with field timecreated
* @return string Link to drilldown table
*/
protected function col_failcounter($record) {
// Get blocked URI, and set as param for page if clicked on
$url = new \moodle_url('/local/csp/csp_report.php', array ('viewviolationclass' => $record->blockeduri));
return \html_writer::link($url, $record->failcounter);
}

/**
* Stop violateddirective from wrapping when long urls are present
*
* @param stdObject $record fieldset object of db table with field timecreated
* @return string Non breaking text line
*/
protected function col_violateddirective($record) {
// Stop line from wrapping
return \html_writer::tag('span', $record->violateddirective, array('style' => 'white-space: nowrap'));
}

/**
* Formatting unix timestamps in column named timecreated to human readable time.
*
Expand Down Expand Up @@ -115,17 +139,57 @@ protected function col_blockeduri($record) {
* @return string HTML link.
*/
protected function col_action($record) {
global $OUTPUT;

$action = new \confirm_action(get_string('areyousuretodeleteonerecord', 'local_csp'));
$url = new \moodle_url($this->baseurl);
$url->params(array(
'removerecordwithhash' => $record->sha1hash,
'sesskey' => sesskey(),
'redirecttopage' => $this->currpage,
));
$actionlink = $OUTPUT->action_link($url, get_string('reset', 'local_csp'), $action);

return $actionlink;
global $OUTPUT, $PAGE;

// Find whether drilldown flag is present in PAGE params
$viewviolationclass = optional_param('viewviolationclass', false, PARAM_TEXT);
if ($viewviolationclass !== false) {
$action = new \confirm_action(get_string('areyousuretodeleteonerecord', 'local_csp'));
$url = new \moodle_url($this->baseurl);
$url->params(array(
'removerecordwithid' => $record->id,
'sesskey' => sesskey(),
'redirecttopage' => $this->currpage,
));
$actionlink = $OUTPUT->action_link($url, get_string('reset', 'local_csp'), $action);

return $actionlink;
} else {
// Else delete entire violation class
$action = new \confirm_action(get_string('areyousuretodeleteonerecord', 'local_csp'));
$url = new \moodle_url($this->baseurl);
$url->params(array(
'removeviolationclass' => $record->blockeduri,
'sesskey' => sesskey(),
'redirecttopage' => $this->currpage,
));
$actionlink = $OUTPUT->action_link($url, get_string('reset', 'local_csp'), $action);

return $actionlink;
}
}

/**
* Gets the 3 highest violater documentURIs for each blockedURI
*
* @param stdObject $record fieldset object of db table with field timecreated
* @return string details of the highest violating documents
*/
protected function col_highestviolaters($record) {
global $DB, $CFG;

// Get 3 highest violaters for each blocked URI
$sql = "SELECT *
FROM {local_csp}
WHERE blockeduri = ?
ORDER BY failcounter DESC";
$violaters = $DB->get_records_sql($sql, array($record->blockeduri), 0, 3);
$return = '';
foreach ($violaters as $violater) {
// Strip the top level domain out of the display
$urlstring = str_replace($CFG->wwwroot, '', $violater->documenturi);
$return .= get_string('highestviolaterscount', 'local_csp', $violater->failcounter).' '.\html_writer::link($violater->documenturi, $urlstring).'<br>';
}
return $return;
}
} // end class csp_report
7 changes: 4 additions & 3 deletions collector.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../config.php');

$inputjson = file_get_contents('php://input');
$cspreport = json_decode($inputjson, true)['csp-report'];

require_once(__DIR__ . '/../../config.php');
global $DB;

if ($cspreport) {
Expand All @@ -50,8 +50,9 @@
echo 'Repeated CSP violation, failcounter incremented.';
} else {
// Insert a new record.
$dataobject->documenturi = $documenturi;
$dataobject->blockeduri = $blockeduri;
// Truncate URIs of extreme length.
$dataobject->documenturi = substr($documenturi, 0, 1024);
$dataobject->blockeduri = substr($blockeduri, 0, 1024);
$dataobject->violateddirective = $cspreport['violated-directive'];
$dataobject->timecreated = time();
$dataobject->sha1hash = $hash;
Expand Down
74 changes: 59 additions & 15 deletions csp_report.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,18 @@

global $DB;

// Remove CSP report record with specified hash. This is triggered from \local_csp\table\csp_report->col_action().
if (($removerecordwithhash = optional_param('removerecordwithhash', false, PARAM_TEXT)) !== false && confirm_sesskey()) {
$DB->delete_records('local_csp', array('sha1hash' => $removerecordwithhash));
// Delete violation class if param set
if (($removeviolationclass = optional_param('removeviolationclass', false, PARAM_TEXT)) !== false && confirm_sesskey()) {
$DB->delete_records('local_csp', array('blockeduri' => $removeviolationclass));
$PAGE->set_url('/local/csp/csp_report.php', array(
'page' => optional_param('redirecttopage', 0, PARAM_INT),
));
redirect($PAGE->url);
}

// Delete individual violation records if set
if (($removerecordwithid = optional_param('removerecordwithid', false, PARAM_TEXT)) !== false && confirm_sesskey()) {
$DB->delete_records('local_csp', array('id' => $removerecordwithid));
$PAGE->set_url('/local/csp/csp_report.php', array(
'page' => optional_param('redirecttopage', 0, PARAM_INT),
));
Expand Down Expand Up @@ -63,11 +72,11 @@
echo $OUTPUT->single_button($urlresetallcspstatistics,
get_string('resetallcspstatistics', 'local_csp'), 'post', array('actions' => array($action)));

$documenturi = get_string('documenturi', 'local_csp');
$blockeduri = get_string('blockeduri', 'local_csp');
$highestviolaters = get_string('highestviolaters', 'local_csp');
$violateddirective = get_string('violateddirective', 'local_csp');
$documenturi = get_string('documenturi', 'local_csp');
$failcounter = get_string('failcounter', 'local_csp');
$timecreated = get_string('timecreated', 'local_csp');
$timeupdated = get_string('timeupdated', 'local_csp');
$action = get_string('action', 'local_csp');

Expand All @@ -76,27 +85,62 @@
$table->sortable(true, 'failcounter', SORT_DESC);
$table->define_columns(array(
'failcounter',
'documenturi',
'blockeduri',
'violateddirective',
'blockeduri',
'highestviolaters',
'timecreated',
'timeupdated',
'action',
));
$table->define_headers(array(
$failcounter,
$documenturi,
$blockeduri,
$violateddirective,
$timecreated,
$blockeduri,
$highestviolaters,
$timeupdated,
$action,
));

$fields = 'id, sha1hash, documenturi, blockeduri, violateddirective, failcounter, timecreated, timeupdated';
$from = '{local_csp}';
$where = '1 = 1';
$table->set_sql($fields, $from, $where);
$viewviolationclass = optional_param('viewviolationclass', false, PARAM_TEXT);
// If user has clicked on a violation to view all violation entries
if ($viewviolationclass !== false) {
$fields = 'id, sha1hash, blockeduri, violateddirective, failcounter, timeupdated, documenturi';
$from = "{local_csp}";
$where = "blockeduri = ?";
$params = array($viewviolationclass);

// Redefine columns to display Violation source
$table->define_columns(array(
'failcounter',
'violateddirective',
'blockeduri',
'documenturi',
'timeupdated',
'action',
));
$table->define_headers(array(
$failcounter,
$violateddirective,
$blockeduri,
$documenturi,
$timeupdated,
$action,
));

} else {
$fields = 'id, blockeduri, violateddirective, failcounter, timecreated';
// Select the first blockedURI of a type, and collapse the rest while summing failcounter
//
$from = "(SELECT MAX(id) AS id,
blockeduri,
violateddirective,
SUM(failcounter) AS failcounter,
MAX(timecreated) AS timecreated
FROM {local_csp}
GROUP BY blockeduri, violateddirective) AS A";
$where = '1 = 1';
$params = array();
}
$table->set_sql($fields, $from, $where, $params);

$table->out(30, true);

Expand Down
Loading

0 comments on commit 834589c

Please sign in to comment.