diff --git a/mod/lti/auth.php b/mod/lti/auth.php
index 831b6315a3a..50cc0b304eb 100644
--- a/mod/lti/auth.php
+++ b/mod/lti/auth.php
@@ -24,6 +24,19 @@
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/lti/locallib.php');
+global $_POST, $_SERVER;
+
+if (!isloggedin() && empty($_POST['repost'])) {
+ header_remove("Set-Cookie");
+ $PAGE->set_pagelayout('popup');
+ $PAGE->set_context(context_system::instance());
+ $output = $PAGE->get_renderer('mod_lti');
+ $page = new \mod_lti\output\repost_crosssite_page($_SERVER['REQUEST_URI'], $_POST);
+ echo $output->header();
+ echo $output->render($page);
+ echo $output->footer();
+ return;
+}
$scope = optional_param('scope', '', PARAM_TEXT);
$responsetype = optional_param('response_type', '', PARAM_TEXT);
diff --git a/mod/lti/classes/output/renderer.php b/mod/lti/classes/output/renderer.php
index b94178da9f7..f2868470264 100644
--- a/mod/lti/classes/output/renderer.php
+++ b/mod/lti/classes/output/renderer.php
@@ -59,4 +59,17 @@ class renderer extends plugin_renderer_base {
$data = $page->export_for_template($this);
return parent::render_from_template('mod_lti/external_registration_return', $data);
}
+
+ /**
+ * Render the reposting of the cross site request.
+ *
+ * @param repost_crosssite_page $page the page renderable.
+ *
+ * @return string rendered html for the page.
+ */
+ public function render_repost_crosssite_page(repost_crosssite_page $page): string {
+ $data = $page->export_for_template($this);
+ return parent::render_from_template('mod_lti/repost_crosssite', $data);
+ }
+
}
diff --git a/mod/lti/classes/output/repost_crosssite_page.php b/mod/lti/classes/output/repost_crosssite_page.php
new file mode 100644
index 00000000000..e7c3501b52e
--- /dev/null
+++ b/mod/lti/classes/output/repost_crosssite_page.php
@@ -0,0 +1,76 @@
+.
+
+/**
+ * Render a page containing a simple form which reposts to self via JS.
+ *
+ * The purpose of this form is to resend a cross-site request to self, which allows the browsers to include the Moodle
+ * session cookie alongside the original POST data, allowing LTI flows to function despite browsers blocking
+ * cross-site cookies.
+ *
+ * @copyright 2021 Cengage
+ * @package mod_lti
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_lti\output;
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->dirroot.'/mod/lti/locallib.php');
+
+use renderable;
+use templatable;
+use renderer_base;
+use stdClass;
+
+/**
+ * Render a page containing a simple form which reposts to self via JS.
+ *
+ * The purpose of this form is to resend a cross-site request to self, which allows the browsers to include the Moodle
+ * session cookie alongside the original POST data, allowing LTI flows to function despite browsers blocking
+ * cross-site cookies.
+ *
+ * @copyright 2021 Cengage
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class repost_crosssite_page implements renderable, templatable {
+
+ /**
+ * Constructor
+ *
+ * @param string $url moodle URL to repost to
+ * @param array $post the POST params to be re-posted
+ */
+ public function __construct(string $url, array $post) {
+ $this->params = array_map(function($k) use ($post) {
+ return ["key" => $k, "value" => str_replace("\"", """, $post[$k])];
+ }, array_keys($post));
+ $this->url = $url;
+ }
+
+ /**
+ * Export this data so it can be used as the context for a mustache template.
+ *
+ * @param renderer_base $output The renderer
+ * @return stdClass Data to be used by the template
+ */
+ public function export_for_template(renderer_base $output) {
+ $renderdata = new stdClass();
+ $renderdata->url = $this->url;
+ $renderdata->params = $this->params;
+ return $renderdata;
+ }
+}
diff --git a/mod/lti/contentitem_return.php b/mod/lti/contentitem_return.php
index 173e26ec864..af819dc1997 100644
--- a/mod/lti/contentitem_return.php
+++ b/mod/lti/contentitem_return.php
@@ -31,6 +31,28 @@ $courseid = required_param('course', PARAM_INT);
$jwt = optional_param('JWT', '', PARAM_RAW);
+$context = context_course::instance($courseid);
+
+$pageurl = new moodle_url('/mod/lti/contentitem_return.php');
+$PAGE->set_url($pageurl);
+$PAGE->set_pagelayout('popup');
+$PAGE->set_context($context);
+
+// Cross-Site causes the cookie to be lost if not POSTed from same site.
+global $_POST;
+if (!empty($_POST["repost"])) {
+ // Unset the param so that LTI 1.1 signature validation passes.
+ unset($_POST["repost"]);
+} else if (!isloggedin()) {
+ header_remove("Set-Cookie");
+ $output = $PAGE->get_renderer('mod_lti');
+ $page = new \mod_lti\output\repost_crosssite_page($_SERVER['REQUEST_URI'], $_POST);
+ echo $output->header();
+ echo $output->render($page);
+ echo $output->footer();
+ return;
+}
+
if (!empty($jwt)) {
$params = lti_convert_from_jwt($id, $jwt);
$consumerkey = $params['oauth_consumer_key'] ?? '';
@@ -52,7 +74,6 @@ if (!empty($jwt)) {
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
require_login($course);
require_sesskey();
-$context = context_course::instance($courseid);
require_capability('moodle/course:manageactivities', $context);
require_capability('mod/lti:addcoursetool', $context);
@@ -66,9 +87,6 @@ if (empty($errormsg) && !empty($items)) {
}
}
-$pageurl = new moodle_url('/mod/lti/contentitem_return.php');
-$PAGE->set_url($pageurl);
-$PAGE->set_pagelayout('popup');
echo $OUTPUT->header();
// Call JS module to redirect the user to the course page or close the dialogue on error/cancel.
diff --git a/mod/lti/templates/repost_crosssite.mustache b/mod/lti/templates/repost_crosssite.mustache
new file mode 100644
index 00000000000..84cacbc32b9
--- /dev/null
+++ b/mod/lti/templates/repost_crosssite.mustache
@@ -0,0 +1,49 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle 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.
+
+ Moodle 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 Moodle. If not, see