Skip to content

Commit

Permalink
Consumer launch is now working... next up, get Provider to respond
Browse files Browse the repository at this point in the history
  • Loading branch information
jazzmind committed Sep 21, 2015
1 parent 7091e6b commit 8fda82b
Show file tree
Hide file tree
Showing 34 changed files with 2,068 additions and 44 deletions.
127 changes: 127 additions & 0 deletions Config/Migration/1440020101_lti.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php
class Lti extends CakeMigration {

/**
* Migration description
*
* @var string
*/
public $description = 'Add LTI support';

/**
* Actions to be performed
*
* @var array $migration
*/
public $migration = [
'up' => [
'create_table' => [
'lti_consumers' => [
'consumer_key' => ['type' => 'string', 'null' => false, 'length' => 255],
'name' => ['type' => 'string', 'null' => false, 'length' => 45],
'secret' => ['type' => 'string', 'null' => false, 'length' => 32],
'lti_version' => ['type' => 'string', 'null' => true, 'length' => 12],
'consumer_name' => ['type' => 'string', 'null' => true, 'length' => 255],
'consumer_version' => ['type' => 'string', 'null' => true, 'length' => 255],
'consumer_guid' => ['type' => 'string', 'null' => true, 'length' => 255],
'css_path' => ['type' => 'string', 'null' => true, 'length' => 255],
'protected' => ['type' => 'boolean', 'null' => false],
'enabled' => ['type' => 'boolean', 'null' => false],
'enable_from' => ['type' => 'datetime', 'null' => true],
'enable_until' => ['type' => 'datetime', 'null' => true],
'last_access' => ['type' => 'date', 'null' => true],
'created' => ['type' => 'datetime', 'null' => false],
'modified' => ['type' => 'datetime', 'null' => false],
'indexes' => [
'PRIMARY' => ['unique' => true, 'column' => 'consumer_key'],
],
'tableParameters' => [],
],
'lti_contexts' => [
'consumer_key' => ['type' => 'string', 'null' => false, 'length' => 255],
'context_id' => ['type' => 'string', 'null' => false, 'length' => 255],
'lti_context_id' => ['type' => 'string', 'null' => true, 'length' => 255],
'lti_resource_id' => ['type' => 'string', 'null' => true, 'length' => 255],
'title' => ['type' => 'string', 'null' => false, 'length' => 255],
'primary_consumer_key' => ['type' => 'string', 'null' => true, 'length' => 255],
'primary_context_id' => ['type' => 'string', 'null' => true, 'length' => 255],
'share_approved' => ['type' => 'boolean', 'null' => true],
'settings' => ['type' => 'text', 'null' => true, 'length' => 1073741824],
'created' => ['type' => 'datetime', 'null' => false],
'modified' => ['type' => 'datetime', 'null' => false],
'indexes' => [
'context_unique' => ['unique' => true, 'column' => ['consumer_key', 'context_id']],
],
'tableParameters' => [],
],
'lti_users' => [
'consumer_key' => ['type' => 'string', 'null' => false, 'length' => 255],
'context_id' => ['type' => 'string', 'null' => false, 'length' => 255],
'user_id' => ['type' => 'string', 'null' => false, 'length' => 255],
'lti_result_sourcedid' => ['type' => 'string', 'null' => false, 'length' => 255],
'created' => ['type' => 'datetime', 'null' => false],
'modified' => ['type' => 'datetime', 'null' => false],
'indexes' => [
'user_unique' => ['unique' => true, 'column' => ['consumer_key', 'context_id', 'user_id']],
],
'tableParameters' => [],
],

'lti_nonces' => [
'consumer_key' => ['type' => 'string', 'null' => false, 'length' => 255],
'value' => ['type' => 'string', 'null' => false, 'length' => 32],
'expires' => ['type' => 'datetime', 'null' => false],
'indexes' => [
'nonce_unique' => ['unique' => true, 'column' => ['consumer_key', 'value']],
],
'tableParameters' => [],
],

'lti_share_keys' => [
'share_key_id' => ['type' => 'string', 'null' => false, 'length' => 32],
'primary_consumer_key' => ['type' => 'string', 'null' => false, 'length' => 255],
'primary_context_id' => ['type' => 'string', 'null' => false, 'length' => 255],
'auto_approve' => ['type' => 'boolean', 'null' => false],
'expires' => ['type' => 'datetime', 'null' => false],
'indexes' => [
'PRIMARY' => ['unique' => true, 'column' => 'share_key_id'],
],
'tableParameters' => [],
],

],


// 'create_field' => [
// 'core_programs' => [
// 'dashboard_content' => ['type' => 'text', 'null' => true, 'length' => 1073741824],
// ],
// ],
],
'down' => [
'drop_table' => [
'lti_consumers', 'lti_contexts', 'lti_users', 'lti_share_keys', 'lti_nonces'
]
],
];

/**
* Before migration callback
*
* @param string $direction Direction of migration process (up or down)
* @return bool Should process continue
*/
public function before($direction) {
return true;
}

/**
* After migration callback
*
* @param string $direction Direction of migration process (up or down)
* @return bool Should process continue
*/
public function after($direction) {
return true;
}
}
183 changes: 178 additions & 5 deletions Controller/ConsumersController.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php
App::uses('LTIAppController', 'LTI.Controller');
App::uses('OAuth', 'LTI.Lib');
App::uses('LtiAppController', 'Lti.Controller');
App::import('Vendor', 'Lti.OAuth', ['file' => 'OAuth.php']);

class ProvidersController extends LTIAppController {
class ConsumersController extends LtiAppController {
public $components = [];

/**
Expand All @@ -12,6 +12,7 @@ class ProvidersController extends LTIAppController {
*/
public $actions = [
'all' => [
'launch', 'response'
],
'admin' => [
'admin_index'/*, 'admin_add', 'admin_edit', 'admin_delete', 'admin_award', 'admin_conditions', 'admin_condition_remove' */
Expand All @@ -21,7 +22,7 @@ class ProvidersController extends LTIAppController {
];

public function beforeFilter() {
$this->Security->unlockedActions = ['admin_index'];
$this->Security->unlockedActions = ['admin_index', 'launch', 'response'];
parent::beforeFilter();
}

Expand All @@ -33,6 +34,159 @@ public function isAuthorized($user) {
public function admin_index() {
}

public function launch() {
$launch_url = Router::url(['plugin' => 'lti', 'admin'=>false, 'controller' => 'providers', 'action' => 'request'], true);
$return_url = Router::url(['plugin' => 'lti', 'admin'=>false, 'controller' => 'consumers', 'action' => 'response'], true);
$outcome_url = Router::url(['plugin' => 'lti', 'admin'=>false, 'controller' => 'consumers', 'action' => 'outcome'], true);
$default_lmsdata = [
"resource_link_id" => "120988f929-274612",
"resource_link_title" => "Weekly Blog",
"resource_link_description" => "A weekly blog.",
"user_id" => "292832126",
"roles" => "Instructor", // or Learner
"lis_person_name_full" => 'Jane Q. Public',
"lis_person_name_family" => 'Public',
"lis_person_name_given" => 'Jane',
"lis_person_contact_email_primary" => "[email protected]",
"lis_person_sourcedid" => "school.edu:user",
"context_id" => "456434513",
"context_title" => "Design of Personal Environments",
"context_label" => "SI182",
"tool_consumer_info_product_family_code" => "ims",
"tool_consumer_info_version" => "1.1",
"tool_consumer_instance_guid" => "lmsng.school.edu",
"tool_consumer_instance_description" => "University of School (LMSng)",
"launch_presentation_locale" => "en-US",
"launch_presentation_document_target" => "frame",
"launch_presentation_width" => null,
"launch_presentation_height" => null,
"launch_presentation_css_url"=> "http://www.imsglobal.org/developers/LTI/test/v1p1/lms.css",
];

$default_desc = str_replace("CUR_URL", $launch_url,
'<?xml version="1.0" encoding="UTF-8"?>
<basic_lti_link xmlns="http://www.imsglobal.org/services/cc/imsblti_v1p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<title>A Simple Descriptor</title>
<custom>
<parameter key="Cool:Factor">120</parameter>
</custom>
<launch_url>CUR_URL</launch_url>
</basic_lti_link>
');

$default_vars = [
'key' => "12345",
'secret' => 'secret',
'endpoint' => $launch_url,
'urlformat' => false,
'format' => '',
'xmldesc' => $default_desc,
];

foreach ($default_lmsdata as $k => $val ) {
$lmsdata[$k] = $val;
if (!empty($this->request->data['Consumer'][$k]) ) {
$lmsdata[$k] = $this->request->data['Consumer'][$k];
}
}

foreach ($default_vars as $k => $v) {
$$k = $v;
if ( !empty($this->request->data['Consumer'][$k]) ) {
$$k = $this->request->data['Consumer'][$k];
$default_vars[$k] = $$k;
}
}

$urlformat = ( $format != 'XML' );

$xmldesc = str_replace("\\\"","\"",$xmldesc);

$this->set(compact('lmsdata', 'key', 'secret', 'urlformat', 'endpoint', 'xmldesc'));

$params = [];
if ( $urlformat ) {
$params = $lmsdata;
} else {
$cx = $this->_launchInfo($xmldesc);
$endpoint = $cx["launch_url"];

if ( empty($endpoint) ) {
echo("<p>Error, did not find a launch_url or secure_launch_url in the XML descriptor</p>\n");
exit();
}
$custom = $cx["custom"];
$params = array_merge($lmsdata, $custom);
}

// Cleanup parms before we sign
foreach( $params as $k => $val ) {
if (strlen(trim($params[$k]) ) < 1 ) {
unset($params[$k]);
}
}

// Add oauth_callback to be compliant with the 1.0A spec
$params["oauth_callback"] = "about:blank";
$params["lti_version"] = "LTI-1p0";
$params["lti_message_type"] = "basic-lti-launch-request";
$params["launch_presentation_return_url"] = $return_url;
$params["lis_outcome_service_url"] = $outcome_url;
$params["lis_result_sourcedid"] = "feb-123-456-2929::28883";
$params = $this->_signLaunchParameters($endpoint, "POST", $params, $key, $secret);
// ksort($params);
// pr($params);
$params["ext_submit"] = "Press to Launch"; // this is so we can inject launch buttons with custom text
$this->set("iframeattr", "width=\"100%\" height=\"900\" scrolling=\"auto\" frameborder=\"1\" transparency");
$this->set("params", $params);
$this->request->data['Consumer'] = am($lmsdata, $default_vars, $params);
}

public function response() {
$this->layout = 'basic';
foreach (['lti_msg', 'lti_errormsg', 'lti_log', 'lti_errorlog'] as $var) {
$$var = '';
if (!empty($this->request->query[$var])) {
$$var = $this->request->query[$var];
}
$this->set($var, $$var);
}
}

// Parse a descriptor
protected function _launchInfo($xmldata) {
$xml = new SimpleXMLElement($xmldata);
if ( empty($xml) ) {
echo("Error parsing Descriptor XML\n");
return;
}
$launch_url = $xml->secure_launch_url[0];

if ( empty($launch_url) ) {
$launch_url = $xml->launch_url[0];
}

$launch_url = (string) $launch_url;

$custom = array();
if ( !empty($xml->custom[0]->parameter ) ) {
foreach ( $xml->custom[0]->parameter as $resource) {
$key = (string) $resource['key'];
$key = strtolower($key);
$nk = "";
for($i=0; $i < strlen($key); $i++) {
$ch = substr($key,$i,1);
if ( $ch >= "a" && $ch <= "z" ) $nk .= $ch;
else if ( $ch >= "0" && $ch <= "9" ) $nk .= $ch;
else $nk .= "_";
}
$value = (string) $resource;
$custom["custom_".$nk] = $value;
}
}
return [ "launch_url" => $launch_url, "custom" => $custom ] ;
}


/**
* Is the consumer key available to accept launch requests?
Expand Down Expand Up @@ -65,7 +219,7 @@ public function getIsAvailable() {
*
* @return array Array of signed message parameters
*/
public function signParameters($url, $type, $version, $params) {
protected function _signParameters($url, $type, $version, $params) {

if (!empty($url)) {
// Check for query parameters which need to be included in the signature
Expand Down Expand Up @@ -103,6 +257,25 @@ public function signParameters($url, $type, $version, $params) {

}

protected function _signLaunchParameters($endpoint, $method="POST", $params, $oauth_consumer_key, $oauth_consumer_secret)
{

$token = null;

$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
$consumer = new OAuthConsumer($oauth_consumer_key, $oauth_consumer_secret, NULL);

$acc_req = OAuthRequest::from_consumer_and_token($consumer, $token, $method, $endpoint, $params);
$acc_req->sign_request($hmac_method, $consumer, $token);

// Pass this back up "out of band" for debugging
$last_base_string = $acc_req->get_signature_base_string();
$this->set('last_base_string', $last_base_string);
$params = $acc_req->get_parameters();

return $params;
}

###
### PRIVATE METHOD
###
Expand Down
2 changes: 1 addition & 1 deletion Controller/LTIAppController.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
App::uses('AppController', 'Controller');

class LTIAppController extends AppController {
class LtiAppController extends AppController {
public $components = [];
}
4 changes: 2 additions & 2 deletions Controller/ProvidersController.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
App::uses('LTIAppController', 'LTI.Controller');
App::uses('LtiAppController', 'Lti.Controller');

class ProvidersController extends LTIAppController {
class ProvidersController extends LtiAppController {
public $components = [];

/**
Expand Down
Loading

0 comments on commit 8fda82b

Please sign in to comment.