You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

312 lines
13 KiB

<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* This file contains a class definition for the Tool Proxy resource
*
* @package ltiservice_toolproxy
* @copyright 2014 Vital Source Technologies http://vitalsource.com
* @author Stephen Vickers
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace ltiservice_toolproxy\local\resources;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/lti/OAuth.php');
require_once($CFG->dirroot . '/mod/lti/TrivialStore.php');
// TODO: Switch to core oauthlib once implemented - MDL-30149.
use moodle\mod\lti as lti;
/**
* A resource implementing the Tool Proxy.
*
* @package ltiservice_toolproxy
* @since Moodle 2.8
* @copyright 2014 Vital Source Technologies http://vitalsource.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class toolproxy extends \mod_lti\local\ltiservice\resource_base {
/**
* Class constructor.
*
* @param ltiservice_toolproxy\local\resources\toolproxy $service Service instance
*/
public function __construct($service) {
parent::__construct($service);
$this->id = 'ToolProxy.collection';
$this->template = '/toolproxy';
$this->formats[] = 'application/vnd.ims.lti.v2.toolproxy+json';
$this->methods[] = 'POST';
}
/**
* Execute the request for this resource.
*
* @param mod_lti\local\ltiservice\response $response Response object for this request.
*/
public function execute($response) {
$ok = $this->check_tool(null, $response->get_request_data());
$ok = $ok && ($this->get_service()->get_tool_proxy());
if ($ok) {
$toolproxy = $this->get_service()->get_tool_proxy();
}
if (!$ok) {
$toolproxy = null;
$response->set_code(401);
}
$tools = array();
// Ensure all required elements are present in the Tool Proxy.
if ($ok) {
$toolproxyjson = json_decode($response->get_request_data());
$ok = !empty($toolproxyjson);
if (!$ok) {
debugging('Tool proxy is not properly formed JSON');
} else {
$ok = isset($toolproxyjson->tool_profile->product_instance->product_info->product_family->vendor->code);
$ok = $ok && isset($toolproxyjson->security_contract->shared_secret);
$ok = $ok && isset($toolproxyjson->tool_profile->resource_handler);
if (!$ok) {
debugging('One or more missing elements from tool proxy: vendor code, shared secret or resource handlers');
}
}
}
// Check all capabilities requested were offered.
if ($ok) {
$offeredcapabilities = explode("\n", $toolproxy->capabilityoffered);
$resources = $toolproxyjson->tool_profile->resource_handler;
$errors = array();
foreach ($resources as $resource) {
if (isset($resource->message)) {
foreach ($resource->message as $message) {
if (!in_array($message->message_type, $offeredcapabilities)) {
$errors[] = $message->message_type;
} else if (isset($resource->parameter)) {
foreach ($message->parameter as $parameter) {
if (isset($parameter->variable) && !in_array($parameter->variable, $offeredcapabilities)) {
$errors[] = $parameter->variable;
}
}
}
}
}
}
if (count($errors) > 0) {
$ok = false;
debugging('Tool proxy contains capabilities which were not offered: ' . implode(', ', $errors));
}
}
// Check all services requested were offered (only tool services currently supported).
$requestsbasicoutcomes = false;
if ($ok && isset($toolproxyjson->security_contract->tool_service)) {
$contexts = lti_get_contexts($toolproxyjson);
$profileservice = lti_get_service_by_name('profile');
$profileservice->set_tool_proxy($toolproxy);
$context = $profileservice->get_service_path() . $profileservice->get_resources()[0]->get_path() . '#';
$offeredservices = explode("\n", $toolproxy->serviceoffered);
$services = lti_get_services();
$tpservices = $toolproxyjson->security_contract->tool_service;
$errors = array();
foreach ($tpservices as $service) {
$fqid = lti_get_fqid($contexts, $service->service);
$requestsbasicoutcomes = $requestsbasicoutcomes || (substr($fqid, -13) === 'Outcomes.LTI1');
if (substr($fqid, 0, strlen($context)) !== $context) {
$errors[] = $service->service;
} else {
$id = explode('#', $fqid, 2);
$aservice = lti_get_service_by_resource_id($services, $id[1]);
$classname = explode('\\', get_class($aservice));
if (empty($aservice) || !in_array($classname[count($classname) - 1], $offeredservices)) {
$errors[] = $service->service;
}
}
}
if (count($errors) > 0) {
$ok = false;
debugging('Tool proxy contains services which were not offered: ' . implode(', ', $errors));
}
}
// Extract all launchable tools from the resource handlers.
if ($ok) {
$resources = $toolproxyjson->tool_profile->resource_handler;
$messagetypes = [
'basic-lti-launch-request',
'ContentItemSelectionRequest',
];
foreach ($resources as $resource) {
$launchable = false;
$messages = array();
$tool = new \stdClass();
$iconinfo = null;
if (is_array($resource->icon_info)) {
$iconinfo = $resource->icon_info[0];
} else {
$iconinfo = $resource->icon_info;
}
if (isset($iconinfo) && isset($iconinfo->default_location) && isset($iconinfo->default_location->path)) {
$tool->iconpath = $iconinfo->default_location->path;
}
foreach ($resource->message as $message) {
if (in_array($message->message_type, $messagetypes)) {
$launchable = $launchable || ($message->message_type === 'basic-lti-launch-request');
$messages[$message->message_type] = $message;
}
}
if (!$launchable) {
continue;
}
$tool->name = $resource->resource_name->default_value;
$tool->messages = $messages;
$tools[] = $tool;
}
$ok = count($tools) > 0;
if (!$ok) {
debugging('No launchable messages found in tool proxy');
}
}
// Add tools and custom parameters.
if ($ok) {
$baseurl = '';
if (isset($toolproxyjson->tool_profile->base_url_choice[0]->default_base_url)) {
$baseurl = $toolproxyjson->tool_profile->base_url_choice[0]->default_base_url;
}
$securebaseurl = '';
if (isset($toolproxyjson->tool_profile->base_url_choice[0]->secure_base_url)) {
$securebaseurl = $toolproxyjson->tool_profile->base_url_choice[0]->secure_base_url;
}
foreach ($tools as $tool) {
$messages = $tool->messages;
$launchrequest = $messages['basic-lti-launch-request'];
$config = new \stdClass();
$config->lti_toolurl = "{$baseurl}{$launchrequest->path}";
$config->lti_typename = $tool->name;
$config->lti_coursevisible = 1;
$config->lti_forcessl = 0;
if (isset($messages['ContentItemSelectionRequest'])) {
$contentitemrequest = $messages['ContentItemSelectionRequest'];
$config->lti_contentitem = 1;
if ($launchrequest->path !== $contentitemrequest->path) {
$config->lti_toolurl_ContentItemSelectionRequest = $baseurl . $contentitemrequest->path;
}
$contentitemcapabilities = implode("\n", $contentitemrequest->enabled_capability);
$config->lti_enabledcapability_ContentItemSelectionRequest = $contentitemcapabilities;
$contentitemparams = self::lti_extract_parameters($contentitemrequest->parameter);
$config->lti_parameter_ContentItemSelectionRequest = $contentitemparams;
}
$type = new \stdClass();
$type->state = LTI_TOOL_STATE_PENDING;
$type->ltiversion = LTI_VERSION_2;
$type->toolproxyid = $toolproxy->id;
// Ensure gradebook column is created.
if ($requestsbasicoutcomes && !in_array('BasicOutcome.url', $launchrequest->enabled_capability)) {
$launchrequest->enabled_capability[] = 'BasicOutcome.url';
}
if ($requestsbasicoutcomes && !in_array('BasicOutcome.sourcedId', $launchrequest->enabled_capability)) {
$launchrequest->enabled_capability[] = 'BasicOutcome.sourcedId';
}
$type->enabledcapability = implode("\n", $launchrequest->enabled_capability);
$type->parameter = self::lti_extract_parameters($launchrequest->parameter);
if (!empty($tool->iconpath)) {
$type->icon = "{$baseurl}{$tool->iconpath}";
if (!empty($securebaseurl)) {
$type->secureicon = "{$securebaseurl}{$tool->iconpath}";
}
}
$ok = $ok && (lti_add_type($type, $config) !== false);
}
if (isset($toolproxyjson->custom)) {
lti_set_tool_settings($toolproxyjson->custom, $toolproxy->id);
}
}
if (!empty($toolproxy)) {
if ($ok) {
// If all went OK accept the tool proxy.
$toolproxy->state = LTI_TOOL_PROXY_STATE_ACCEPTED;
$toolproxy->toolproxy = $response->get_request_data();
$toolproxy->secret = $toolproxyjson->security_contract->shared_secret;
$toolproxy->vendorcode = $toolproxyjson->tool_profile->product_instance->product_info->product_family->vendor->code;
$url = $this->get_endpoint();
$body = <<< EOD
{
"@context" : "http://purl.imsglobal.org/ctx/lti/v2/ToolProxyId",
"@type" : "ToolProxy",
"@id" : "{$url}",
"tool_proxy_guid" : "{$toolproxy->guid}"
}
EOD;
$response->set_code(201);
$response->set_content_type('application/vnd.ims.lti.v2.toolproxy.id+json');
$response->set_body($body);
} else {
// Otherwise reject the tool proxy.
$toolproxy->state = LTI_TOOL_PROXY_STATE_REJECTED;
$response->set_code(400);
}
lti_update_tool_proxy($toolproxy);
} else {
$response->set_code(400);
}
}
/**
* Extracts the message parameters from the tool proxy entry
*
* @param array $parameters Parameter section of a message
*
* @return String containing parameters
*/
private static function lti_extract_parameters($parameters) {
$params = array();
foreach ($parameters as $parameter) {
if (isset($parameter->variable)) {
$value = '$' . $parameter->variable;
} else {
$value = $parameter->fixed;
if (strlen($value) > 0) {
$first = substr($value, 0, 1);
if (($first == '$') || ($first == '\\')) {
$value = '\\' . $value;
}
}
}
$params[] = "{$parameter->name}={$value}";
}
return implode("\n", $params);
}
}