From b9a7c8c45164463b290498cf9aa26c0ceca772ed Mon Sep 17 00:00:00 2001 From: "[Peter Burnett]" <[peterburnett@catalyst-au.net]> Date: Tue, 24 Sep 2019 13:45:38 +1000 Subject: [PATCH 1/9] Removed column for sourceURI, and aggregated target URI rows with a DB query --- classes/table/csp_report.php | 2 +- csp_report.php | 34 +++++++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/classes/table/csp_report.php b/classes/table/csp_report.php index cd61db6..11f54d8 100644 --- a/classes/table/csp_report.php +++ b/classes/table/csp_report.php @@ -120,7 +120,7 @@ protected function col_action($record) { $action = new \confirm_action(get_string('areyousuretodeleteonerecord', 'local_csp')); $url = new \moodle_url($this->baseurl); $url->params(array( - 'removerecordwithhash' => $record->sha1hash, + 'removeviolationclass' => $record->blockeduri, 'sesskey' => sesskey(), 'redirecttopage' => $this->currpage, )); diff --git a/csp_report.php b/csp_report.php index 5b25985..dd5a8f0 100644 --- a/csp_report.php +++ b/csp_report.php @@ -28,9 +28,17 @@ global $DB; -// Remove CSP report record with specified hash. This is triggered from \local_csp\table\csp_report->col_action(). +/*// 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)); + //$DB->delete_records('local_csp', array('sha1hash' => $removerecordwithhash)); + $PAGE->set_url('/local/csp/csp_report.php', array( + 'page' => optional_param('redirecttopage', 0, PARAM_INT), + )); + redirect($PAGE->url); +}*/ + +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), )); @@ -63,7 +71,6 @@ 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'); $violateddirective = get_string('violateddirective', 'local_csp'); $failcounter = get_string('failcounter', 'local_csp'); @@ -76,7 +83,6 @@ $table->sortable(true, 'failcounter', SORT_DESC); $table->define_columns(array( 'failcounter', - 'documenturi', 'blockeduri', 'violateddirective', 'timecreated', @@ -85,7 +91,6 @@ )); $table->define_headers(array( $failcounter, - $documenturi, $blockeduri, $violateddirective, $timecreated, @@ -93,8 +98,23 @@ $action, )); -$fields = 'id, sha1hash, documenturi, blockeduri, violateddirective, failcounter, timecreated, timeupdated'; -$from = '{local_csp}'; +$fields = 'id, sha1hash, blockeduri, violateddirective, failcounter, timecreated, timeupdated'; +// Select the first blockedURI of a type, and collapse the rest while summing failcounter +// Then grab other fields from the table where id is the selected collapsed ID +$from = "(SELECT A.id, + A.blockeduri, + A.failcounter, + B.violateddirective, + B.sha1hash, + B.timecreated, + B.timeupdated + FROM ( + SELECT MIN(id) AS id, + blockeduri, + SUM(failcounter) AS failcounter + FROM {local_csp} GROUP BY blockeduri) AS A, + {local_csp} as B + WHERE A.id = B.id) AS subquery"; $where = '1 = 1'; $table->set_sql($fields, $from, $where); From 08b992d7fca3dff74cb5771921024caad9d26dbd Mon Sep 17 00:00:00 2001 From: "[Peter Burnett]" <[peterburnett@catalyst-au.net]> Date: Tue, 24 Sep 2019 14:39:36 +1000 Subject: [PATCH 2/9] Added column for top violaters of CSP --- classes/table/csp_report.php | 17 +++++++++++++++++ collector.php | 2 +- csp_report.php | 21 ++++++++++++--------- lang/en/local_csp.php | 2 ++ samples/sample.html | 2 +- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/classes/table/csp_report.php b/classes/table/csp_report.php index 11f54d8..dea27f3 100644 --- a/classes/table/csp_report.php +++ b/classes/table/csp_report.php @@ -128,4 +128,21 @@ protected function col_action($record) { return $actionlink; } + + protected function col_highestviolaters($record) { + global $DB; + + // 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) { + $return .= \html_writer::link($violater->documenturi, $violater->documenturi).'
'; + $return .= get_string('highestviolaterscount', 'local_csp', $violater->failcounter).'
'; + } + return $return; + } } // end class csp_report diff --git a/collector.php b/collector.php index 024ca06..f63c025 100644 --- a/collector.php +++ b/collector.php @@ -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) { diff --git a/csp_report.php b/csp_report.php index dd5a8f0..d5d0c17 100644 --- a/csp_report.php +++ b/csp_report.php @@ -72,6 +72,7 @@ get_string('resetallcspstatistics', 'local_csp'), 'post', array('actions' => array($action))); $blockeduri = get_string('blockeduri', 'local_csp'); +$highestviolaters = get_string('highestviolaters', 'local_csp'); $violateddirective = get_string('violateddirective', 'local_csp'); $failcounter = get_string('failcounter', 'local_csp'); $timecreated = get_string('timecreated', 'local_csp'); @@ -84,6 +85,7 @@ $table->define_columns(array( 'failcounter', 'blockeduri', + 'highestviolaters', 'violateddirective', 'timecreated', 'timeupdated', @@ -92,6 +94,7 @@ $table->define_headers(array( $failcounter, $blockeduri, + $highestviolaters, $violateddirective, $timecreated, $timeupdated, @@ -103,17 +106,17 @@ // Then grab other fields from the table where id is the selected collapsed ID $from = "(SELECT A.id, A.blockeduri, - A.failcounter, - B.violateddirective, - B.sha1hash, - B.timecreated, - B.timeupdated + A.failcounter, + B.violateddirective, + B.sha1hash, + B.timecreated, + B.timeupdated FROM ( - SELECT MIN(id) AS id, - blockeduri, - SUM(failcounter) AS failcounter + SELECT MAX(id) AS id, + blockeduri, + SUM(failcounter) AS failcounter FROM {local_csp} GROUP BY blockeduri) AS A, - {local_csp} as B + {local_csp} as B WHERE A.id = B.id) AS subquery"; $where = '1 = 1'; $table->set_sql($fields, $from, $where); diff --git a/lang/en/local_csp.php b/lang/en/local_csp.php index b8f489a..0cbf5f4 100644 --- a/lang/en/local_csp.php +++ b/lang/en/local_csp.php @@ -46,6 +46,8 @@ $string['cspsettingsinfo'] = '

CSP works through adding a special HTTP response header to every Moodle page. Modern browsers, when they see this header, are able to perform certain actions e.g. block insecure content on such pages. Please read more about CSP here.

If you leave any of these settings blank CSP headers will not be used.

'; $string['documenturi'] = 'Violation at'; $string['failcounter'] = 'Count'; +$string['highestviolaters'] = 'Top Violation Sources'; +$string['highestviolaterscount'] = 'Count: {$a}'; $string['loadingmixedcontentdescription'] = 'When accessing moodle website via HTTPS browser prohibits displaying of the below resources because they origin from HTTP.
You should be able to see it in your browser\'s Javascript console.'; $string['loadinsecurecss'] = 'Load insecure css from {$a}'; $string['loadinsecureiframe'] = 'Load insecure iframe from {$a}'; diff --git a/samples/sample.html b/samples/sample.html index efc9aa1..e0656fc 100644 --- a/samples/sample.html +++ b/samples/sample.html @@ -7,4 +7,4 @@

Successfully loaded sample.html!

- \ No newline at end of file + From 29b93f1f4316a24a8536a56dbf55880232a27e8f Mon Sep 17 00:00:00 2001 From: "[Peter Burnett]" <[peterburnett@catalyst-au.net]> Date: Tue, 24 Sep 2019 15:39:22 +1000 Subject: [PATCH 3/9] Lots of misc table cleanup --- .travis.yml | 2 -- classes/table/csp_report.php | 7 ++++--- csp_report.php | 7 ++----- lang/en/local_csp.php | 3 +-- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7c854e1..18b144f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ language: php notifications: email: recipients: - - brendan.heywood@gmail.com - - suankan@catalyst-au.net sudo: false diff --git a/classes/table/csp_report.php b/classes/table/csp_report.php index dea27f3..e7da073 100644 --- a/classes/table/csp_report.php +++ b/classes/table/csp_report.php @@ -130,7 +130,7 @@ protected function col_action($record) { } protected function col_highestviolaters($record) { - global $DB; + global $DB, $CFG; // Get 3 highest violaters for each blocked URI $sql = "SELECT * @@ -140,8 +140,9 @@ protected function col_highestviolaters($record) { $violaters = $DB->get_records_sql($sql, array($record->blockeduri), 0, 3); $return = ''; foreach ($violaters as $violater) { - $return .= \html_writer::link($violater->documenturi, $violater->documenturi).'
'; - $return .= get_string('highestviolaterscount', 'local_csp', $violater->failcounter).'
'; + // Strip the top level domain out of the display + $urlstring = str_replace($CFG->wwwroot, '', $violater->documenturi); + $return .= $violater->failcounter.' '.\html_writer::link($violater->documenturi, $urlstring).'
'; } return $return; } diff --git a/csp_report.php b/csp_report.php index d5d0c17..cbbcc54 100644 --- a/csp_report.php +++ b/csp_report.php @@ -75,7 +75,6 @@ $highestviolaters = get_string('highestviolaters', 'local_csp'); $violateddirective = get_string('violateddirective', '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'); @@ -84,19 +83,17 @@ $table->sortable(true, 'failcounter', SORT_DESC); $table->define_columns(array( 'failcounter', + 'violateddirective', 'blockeduri', 'highestviolaters', - 'violateddirective', - 'timecreated', 'timeupdated', 'action', )); $table->define_headers(array( $failcounter, + $violateddirective, $blockeduri, $highestviolaters, - $violateddirective, - $timecreated, $timeupdated, $action, )); diff --git a/lang/en/local_csp.php b/lang/en/local_csp.php index 0cbf5f4..fb9bd97 100644 --- a/lang/en/local_csp.php +++ b/lang/en/local_csp.php @@ -45,7 +45,7 @@ $string['cspsettings'] = 'Content security policy settings'; $string['cspsettingsinfo'] = '

CSP works through adding a special HTTP response header to every Moodle page. Modern browsers, when they see this header, are able to perform certain actions e.g. block insecure content on such pages. Please read more about CSP here.

If you leave any of these settings blank CSP headers will not be used.

'; $string['documenturi'] = 'Violation at'; -$string['failcounter'] = 'Count'; +$string['failcounter'] = '#'; $string['highestviolaters'] = 'Top Violation Sources'; $string['highestviolaterscount'] = 'Count: {$a}'; $string['loadingmixedcontentdescription'] = 'When accessing moodle website via HTTPS browser prohibits displaying of the below resources because they origin from HTTP.
You should be able to see it in your browser\'s Javascript console.'; @@ -60,7 +60,6 @@ $string['reset'] = 'Reset'; $string['resetallcspstatistics'] = 'Reset all statistics'; $string['scspheadernone'] = 'Not used'; -$string['timecreated'] = 'Recorded'; $string['timeupdated'] = 'Last'; $string['violateddirective'] = 'Policy'; $string['privacy:metadata'] = 'The CSP plugin contains no user specific data.'; From ab73308d32ca49e4197551c089fdf888f639bd2b Mon Sep 17 00:00:00 2001 From: "[Peter Burnett]" <[peterburnett@catalyst-au.net]> Date: Tue, 24 Sep 2019 16:41:21 +1000 Subject: [PATCH 4/9] Added additional table data query for getting data for a single violation class --- .travis.yml | 48 ++++++++++++++---------- classes/table/csp_report.php | 32 +++++++++++++++- csp_report.php | 71 +++++++++++++++++++++--------------- version.php | 4 +- 4 files changed, 104 insertions(+), 51 deletions(-) diff --git a/.travis.yml b/.travis.yml index 18b144f..ae843de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,49 +6,59 @@ notifications: 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 ../.. diff --git a/classes/table/csp_report.php b/classes/table/csp_report.php index e7da073..622c705 100644 --- a/classes/table/csp_report.php +++ b/classes/table/csp_report.php @@ -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. * @@ -129,6 +153,12 @@ protected function col_action($record) { 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; @@ -142,7 +172,7 @@ protected function col_highestviolaters($record) { foreach ($violaters as $violater) { // Strip the top level domain out of the display $urlstring = str_replace($CFG->wwwroot, '', $violater->documenturi); - $return .= $violater->failcounter.' '.\html_writer::link($violater->documenturi, $urlstring).'
'; + $return .= get_string('highestviolaterscount', 'local_csp', $violater->failcounter).' '.\html_writer::link($violater->documenturi, $urlstring).'
'; } return $return; } diff --git a/csp_report.php b/csp_report.php index cbbcc54..798a7f1 100644 --- a/csp_report.php +++ b/csp_report.php @@ -28,15 +28,6 @@ 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)); - $PAGE->set_url('/local/csp/csp_report.php', array( - 'page' => optional_param('redirecttopage', 0, PARAM_INT), - )); - redirect($PAGE->url); -}*/ - 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( @@ -74,6 +65,7 @@ $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'); $timeupdated = get_string('timeupdated', 'local_csp'); $action = get_string('action', 'local_csp'); @@ -86,7 +78,7 @@ 'violateddirective', 'blockeduri', 'highestviolaters', - 'timeupdated', + 'timecreated', 'action', )); $table->define_headers(array( @@ -98,25 +90,46 @@ $action, )); -$fields = 'id, sha1hash, blockeduri, violateddirective, failcounter, timecreated, timeupdated'; -// Select the first blockedURI of a type, and collapse the rest while summing failcounter -// Then grab other fields from the table where id is the selected collapsed ID -$from = "(SELECT A.id, - A.blockeduri, - A.failcounter, - B.violateddirective, - B.sha1hash, - B.timecreated, - B.timeupdated - FROM ( - SELECT MAX(id) AS id, - blockeduri, - SUM(failcounter) AS failcounter - FROM {local_csp} GROUP BY blockeduri) AS A, - {local_csp} as B - WHERE A.id = B.id) AS subquery"; -$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 = 'A.id, B.sha1hash, A.blockeduri, B.violateddirective, A.failcounter, A.timecreated'; + // Select the first blockedURI of a type, and collapse the rest while summing failcounter + // Then grab other fields from the table where id is the selected collapsed ID + $from = "(SELECT MAX(id) AS id, + blockeduri, + SUM(failcounter) AS failcounter, + MAX(timecreated) AS timecreated + FROM {local_csp} GROUP BY blockeduri) AS A, + {local_csp} as B"; + $where = 'A.id = B.id'; + $params = array(); +} +$table->set_sql($fields, $from, $where, $params); $table->out(30, true); diff --git a/version.php b/version.php index 7ce351c..8c75e65 100644 --- a/version.php +++ b/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die; -$plugin->version = 2017041801; -$plugin->release = 2017041801; +$plugin->version = 2019092400; +$plugin->release = 2019092400; $plugin->requires = 2015051100; $plugin->maturity = MATURITY_STABLE; $plugin->component = 'local_csp'; From ed3870a45c93b0a4ffcf00fae7f5a5de80af6d7a Mon Sep 17 00:00:00 2001 From: "[Peter Burnett]" <[peterburnett@catalyst-au.net]> Date: Wed, 25 Sep 2019 12:55:57 +1000 Subject: [PATCH 5/9] Simplified query and readded deleting single records in drilldown menu --- classes/table/csp_report.php | 40 +++++++++++++++++++++++++----------- csp_report.php | 21 ++++++++++++++----- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/classes/table/csp_report.php b/classes/table/csp_report.php index 622c705..94c8cc6 100644 --- a/classes/table/csp_report.php +++ b/classes/table/csp_report.php @@ -139,18 +139,34 @@ 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( - 'removeviolationclass' => $record->blockeduri, - '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; + } } /** diff --git a/csp_report.php b/csp_report.php index 798a7f1..d682e52 100644 --- a/csp_report.php +++ b/csp_report.php @@ -28,6 +28,7 @@ global $DB; +// 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( @@ -36,6 +37,15 @@ 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), + )); + redirect($PAGE->url); +} + $resetallcspstatistics = optional_param('resetallcspstatistics', 0, PARAM_INT); if ($resetallcspstatistics == 1 && confirm_sesskey()) { $DB->delete_records('local_csp'); @@ -117,16 +127,17 @@ )); } else { - $fields = 'A.id, B.sha1hash, A.blockeduri, B.violateddirective, A.failcounter, A.timecreated'; + $fields = 'id, blockeduri, violateddirective, failcounter, timecreated'; // Select the first blockedURI of a type, and collapse the rest while summing failcounter - // Then grab other fields from the table where id is the selected collapsed ID + // $from = "(SELECT MAX(id) AS id, blockeduri, + violateddirective, SUM(failcounter) AS failcounter, MAX(timecreated) AS timecreated - FROM {local_csp} GROUP BY blockeduri) AS A, - {local_csp} as B"; - $where = 'A.id = B.id'; + FROM {local_csp} + GROUP BY blockeduri, violateddirective) AS A"; + $where = '1 = 1'; $params = array(); } $table->set_sql($fields, $from, $where, $params); From 2013da8c5375fcc9c0a93d9bb08a5bb1286e2933 Mon Sep 17 00:00:00 2001 From: "[Peter Burnett]" <[peterburnett@catalyst-au.net]> Date: Tue, 1 Oct 2019 13:08:09 +1000 Subject: [PATCH 6/9] Increased the length allowed for URLs --- collector.php | 5 +++-- db/install.xml | 6 +++--- db/upgrade.php | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ version.php | 4 ++-- 4 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 db/upgrade.php diff --git a/collector.php b/collector.php index f63c025..f3644b1 100644 --- a/collector.php +++ b/collector.php @@ -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; diff --git a/db/install.xml b/db/install.xml index cb27452..7c2018b 100755 --- a/db/install.xml +++ b/db/install.xml @@ -1,5 +1,5 @@ - @@ -7,8 +7,8 @@ - - + + diff --git a/db/upgrade.php b/db/upgrade.php new file mode 100644 index 0000000..6d14c5c --- /dev/null +++ b/db/upgrade.php @@ -0,0 +1,54 @@ +. + +/** + * Upgrade script + * + * @package local_csp + * @author Peter Burnett + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +function xmldb_local_csp_upgrade($oldversion) { + global $DB; + $dbman = $DB->get_manager(); + + if ($oldversion < 2019100100) { + + // Changing precision of field documenturi on table local_csp to (1333). + $table = new xmldb_table('local_csp'); + $field = new xmldb_field('documenturi', XMLDB_TYPE_CHAR, '1333', null, null, null, null, 'id'); + + // Launch change of precision for field documenturi. + $dbman->change_field_precision($table, $field); + + // Changing precision of field blockeduri on table local_csp to (1333). + $table = new xmldb_table('local_csp'); + $field = new xmldb_field('blockeduri', XMLDB_TYPE_CHAR, '1333', null, null, null, null, 'documenturi'); + + // Launch change of precision for field blockeduri. + $dbman->change_field_precision($table, $field); + + // Csp savepoint reached. + upgrade_plugin_savepoint(true, 2019100100, 'local', 'csp'); + } + + return true; +} + diff --git a/version.php b/version.php index 8c75e65..940a2be 100644 --- a/version.php +++ b/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die; -$plugin->version = 2019092400; -$plugin->release = 2019092400; +$plugin->version = 2019100100; +$plugin->release = 2019100100; $plugin->requires = 2015051100; $plugin->maturity = MATURITY_STABLE; $plugin->component = 'local_csp'; From 8627d2da476ea4b47d4c7d4b93d35a1964197d87 Mon Sep 17 00:00:00 2001 From: Brendan Heywood Date: Tue, 1 Oct 2019 15:06:02 +1000 Subject: [PATCH 7/9] Update README.md --- README.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0ec058b..6585cae 100644 --- a/README.md +++ b/README.md @@ -12,25 +12,34 @@ Why would you want this? Security, security, security. This plugin helps you to detect and eliminate 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. 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 git@github.com:catalyst/moodle-local_csp.git local\csp ``` @@ -42,19 +51,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/ Catalyst IT From 0e3fbe95e834baaa6da557a2ea156f944c3b9be0 Mon Sep 17 00:00:00 2001 From: Brendan Heywood Date: Tue, 1 Oct 2019 17:06:17 +1000 Subject: [PATCH 8/9] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6585cae..9958e6e 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,11 @@ 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? ------------- From 778ca66435f9c3dd19bc4e726f207249a14c43d5 Mon Sep 17 00:00:00 2001 From: Brendan Heywood Date: Tue, 1 Oct 2019 17:06:33 +1000 Subject: [PATCH 9/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9958e6e..15bb9b5 100644 --- a/README.md +++ b/README.md @@ -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)