Skip to content

Commit

Permalink
Sakai and Canvas LTI. Handle long referrer urls
Browse files Browse the repository at this point in the history
* overhaul lti test provider to simplify
* simplify lti launch picker code when sending content selection messages to lti/assignment
* fixed issue with starting plays with referrer urls longer than 255 characters fixes ucfopen#1418
* updates oauth validation in fuel by ensuring the request uri is not masked by FuelPHP
  • Loading branch information
iturgeon committed Dec 28, 2022
1 parent 037c84f commit 9c8cae9
Show file tree
Hide file tree
Showing 10 changed files with 362 additions and 518 deletions.
488 changes: 272 additions & 216 deletions fuel/app/classes/controller/lti/test.php

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions fuel/app/classes/controller/widgets.php
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,6 @@ protected function _play_widget($inst_id = false, $demo=false, $is_embedded=fals
// allow events to redirect
if ( ! empty($result['redirect'])) Response::redirect($result['redirect']);

// allow events to render the picker instead
if ( ! empty($result['render_picker'])) return $this->display_picker();

// allow events to set inst_id
if ( ! empty($result['inst_id'])) $inst_id = $result['inst_id'];

Expand Down
11 changes: 0 additions & 11 deletions fuel/app/classes/ltievents.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
class LtiEvents
{
const PLAY_STATE_FIRST_LAUNCH = 'first_launch';
const PLAY_STATE_CONTENT_SELECTION = 'content_selection';
const PLAY_STATE_REPLAY = 'replay';
const PLAY_STATE_NOT_LTI = 'not_lti';
protected static $inst_id;
Expand Down Expand Up @@ -84,13 +83,6 @@ public static function on_before_play_start_event($payload)
$launch = static::session_get_launch($token);
return ['context_id' => $launch->context_id];
}
elseif ($play_state == self::PLAY_STATE_CONTENT_SELECTION)
{
$launch = LtiLaunch::from_request();
if ( ! Oauth::validate_post()) $redirect = '/lti/error?message=invalid_oauth_request';
elseif ( ! LtiUserManager::authenticate($launch)) $redirect = '/lti/error/unknown_user';
return ['render_picker' => true];
}
return [];
}

Expand Down Expand Up @@ -248,9 +240,6 @@ protected static function find_assoc_from_resource_id($resource_id)

protected static function get_lti_play_state($play_id = false)
{
// Is there a resource_link_id? Then this is an LTI launch
if (\Input::param('lti_message_type') === 'ContentItemSelectionRequest') return self::PLAY_STATE_CONTENT_SELECTION;

// Is there a resource_link_id? Then this is an LTI launch
if (\Input::param('resource_link_id')) return self::PLAY_STATE_FIRST_LAUNCH;

Expand Down
2 changes: 2 additions & 0 deletions fuel/app/classes/materia/api/v1.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ static public function widget_instances_get($inst_ids = null)

static public function lti_sign_content_item_selection(string $url, string $content_items, string $lti_key)
{
// if (\Service_User::verify_session() !== true) return Msg::no_login();

$params = [
"lti_message_type" => 'ContentItemSelection',
"lti_version" => 'LTI-1p0',
Expand Down
2 changes: 1 addition & 1 deletion fuel/app/classes/materia/session/play.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public function start($user_id=0, $inst_id=0, $context_id=false, $is_preview=fal
// Essentially true but fragile.
$is_lti = array_key_exists('lti_message_type', $this->environment_data['input']) || array_key_exists('token', $this->environment_data['input']);
$this->auth = $is_lti ? 'lti' : '';
$this->referrer_url = \Input::referrer();
$this->referrer_url = mb_strimwidth(\Input::referrer(), 0, 255);

// Preview Plays dont log anything
if ($is_preview) return static::start_preview($inst_id);
Expand Down
4 changes: 2 additions & 2 deletions fuel/app/classes/oauth.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ public static function validate_post()

$hasher = new \Eher\OAuth\HmacSha1(); // THIS CODE ASSUMES HMACSHA1, could be more versetile, but hey
$consumer = new \Eher\OAuth\Consumer(null, $lti_config['secret']);
$request = \Eher\OAuth\Request::from_consumer_and_token($consumer, null, 'POST', \Uri::current(), \Input::post());
$request = \Eher\OAuth\Request::from_consumer_and_token($consumer, null, 'POST', \Uri::main(), \Input::post());
$new_sig = $request->build_signature($hasher, $consumer, false);

if ($new_sig !== $signature) throw new \Exception('Authorization signature failure.');
return true;
}
catch (\Exception $e)
{
\Materia\Log::profile(['invalid-oauth-received', $e->getMessage(), \Uri::current(), print_r(\Input::post(), 1)], 'lti-error-dump');
\Materia\Log::profile(['invalid-oauth-received', $e->getMessage(), \Uri::main(), print_r(\Input::post(), 1)], 'lti-error-dump');
}

return false;
Expand Down
9 changes: 8 additions & 1 deletion fuel/app/config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@
'preview/(:alnum)(/.*)?' => 'widgets/preview_widget/$1',
'preview-embed/(:alnum)(/.*)?' => 'widgets/play_embedded_preview/$1',
'embed/(:alnum)(/.*)?' => 'widgets/play_embedded/$1',
'lti/assignment?' => 'widgets/play_embedded/$1', // legacy LTI url
'lti/assignment?' => function () {
if(\Input::param('lti_message_type') === 'ContentItemSelectionRequest')
{
return \Request::forge('lti/picker', false)->execute();
}

return \Request::forge('widgets/play_embedded', false)->execute();
},

'data/export/(:alnum)' => 'data/export/$1',
"scores/single/{$play_id}/(:alnum)(/.*)?" => 'scores/single/$1/$2',
Expand Down
31 changes: 31 additions & 0 deletions fuel/app/themes/default/layouts/lti_sign_and_launch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<title>Materia Test as Provider</title>
<meta charset="utf-8" />
<style type="text/css"></style>
<?= Css::render() ?>
<?= Js::render() ?>
</head>
<body>
<p>Signed request created, sending</p>
<form id="form" method="POST">
</form>
</body>
<script type="text/javascript">
const postData = <?= $post ?>;
const formEl = document.getElementById('form');
formEl.action = postData.endpoint;

for(var attr in postData)
{
const i = document.createElement('input');
i.type = 'hidden';
i.name = attr;
i.value = postData[attr];
formEl.appendChild(i);
}

formEl.submit();
</script>
</html>
26 changes: 0 additions & 26 deletions fuel/app/themes/default/layouts/test_learner.php

This file was deleted.

Loading

0 comments on commit 9c8cae9

Please sign in to comment.