1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * This file contains a class definition for the Tool Proxy resource 19 * 20 * @package ltiservice_toolproxy 21 * @copyright 2014 Vital Source Technologies http://vitalsource.com 22 * @author Stephen Vickers 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 27 namespace ltiservice_toolproxy\local\resources; 28 29 defined('MOODLE_INTERNAL') || die(); 30 31 require_once($CFG->dirroot . '/mod/lti/OAuth.php'); 32 require_once($CFG->dirroot . '/mod/lti/TrivialStore.php'); 33 34 // TODO: Switch to core oauthlib once implemented - MDL-30149. 35 use moodle\mod\lti as lti; 36 37 /** 38 * A resource implementing the Tool Proxy. 39 * 40 * @package ltiservice_toolproxy 41 * @since Moodle 2.8 42 * @copyright 2014 Vital Source Technologies http://vitalsource.com 43 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 44 */ 45 class toolproxy extends \mod_lti\local\ltiservice\resource_base { 46 47 /** 48 * Class constructor. 49 * 50 * @param ltiservice_toolproxy\local\resources\toolproxy $service Service instance 51 */ 52 public function __construct($service) { 53 54 parent::__construct($service); 55 $this->id = 'ToolProxy.collection'; 56 $this->template = '/toolproxy'; 57 $this->formats[] = 'application/vnd.ims.lti.v2.toolproxy+json'; 58 $this->methods[] = 'POST'; 59 60 } 61 62 /** 63 * Execute the request for this resource. 64 * 65 * @param mod_lti\local\ltiservice\response $response Response object for this request. 66 */ 67 public function execute($response) { 68 69 $ok = $this->check_tool(null, $response->get_request_data()); 70 $ok = $ok && ($this->get_service()->get_tool_proxy()); 71 if ($ok) { 72 $toolproxy = $this->get_service()->get_tool_proxy(); 73 } 74 if (!$ok) { 75 $toolproxy = null; 76 $response->set_code(401); 77 } 78 $tools = array(); 79 80 // Ensure all required elements are present in the Tool Proxy. 81 if ($ok) { 82 $toolproxyjson = json_decode($response->get_request_data()); 83 $ok = !empty($toolproxyjson); 84 if (!$ok) { 85 debugging('Tool proxy is not properly formed JSON'); 86 } else { 87 $ok = isset($toolproxyjson->tool_profile->product_instance->product_info->product_family->vendor->code); 88 $ok = $ok && isset($toolproxyjson->security_contract->shared_secret); 89 $ok = $ok && isset($toolproxyjson->tool_profile->resource_handler); 90 if (!$ok) { 91 debugging('One or more missing elements from tool proxy: vendor code, shared secret or resource handlers'); 92 } 93 } 94 } 95 96 // Check all capabilities requested were offered. 97 if ($ok) { 98 $offeredcapabilities = explode("\n", $toolproxy->capabilityoffered); 99 $resources = $toolproxyjson->tool_profile->resource_handler; 100 $errors = array(); 101 foreach ($resources as $resource) { 102 if (isset($resource->message)) { 103 foreach ($resource->message as $message) { 104 if (!in_array($message->message_type, $offeredcapabilities)) { 105 $errors[] = $message->message_type; 106 } else if (isset($resource->parameter)) { 107 foreach ($message->parameter as $parameter) { 108 if (isset($parameter->variable) && !in_array($parameter->variable, $offeredcapabilities)) { 109 $errors[] = $parameter->variable; 110 } 111 } 112 } 113 } 114 } 115 } 116 if (count($errors) > 0) { 117 $ok = false; 118 debugging('Tool proxy contains capabilities which were not offered: ' . implode(', ', $errors)); 119 } 120 } 121 122 // Check all services requested were offered (only tool services currently supported). 123 $requestsbasicoutcomes = false; 124 if ($ok && isset($toolproxyjson->security_contract->tool_service)) { 125 $contexts = lti_get_contexts($toolproxyjson); 126 $profileservice = lti_get_service_by_name('profile'); 127 $profileservice->set_tool_proxy($toolproxy); 128 $context = $profileservice->get_service_path() . $profileservice->get_resources()[0]->get_path() . '#'; 129 $offeredservices = explode("\n", $toolproxy->serviceoffered); 130 $services = lti_get_services(); 131 $tpservices = $toolproxyjson->security_contract->tool_service; 132 $errors = array(); 133 foreach ($tpservices as $service) { 134 $fqid = lti_get_fqid($contexts, $service->service); 135 $requestsbasicoutcomes = $requestsbasicoutcomes || (substr($fqid, -13) === 'Outcomes.LTI1'); 136 if (substr($fqid, 0, strlen($context)) !== $context) { 137 $errors[] = $service->service; 138 } else { 139 $id = explode('#', $fqid, 2); 140 $aservice = lti_get_service_by_resource_id($services, $id[1]); 141 $classname = explode('\\', get_class($aservice)); 142 if (empty($aservice) || !in_array($classname[count($classname) - 1], $offeredservices)) { 143 $errors[] = $service->service; 144 } 145 } 146 } 147 if (count($errors) > 0) { 148 $ok = false; 149 debugging('Tool proxy contains services which were not offered: ' . implode(', ', $errors)); 150 } 151 } 152 153 // Extract all launchable tools from the resource handlers. 154 if ($ok) { 155 $resources = $toolproxyjson->tool_profile->resource_handler; 156 $messagetypes = [ 157 'basic-lti-launch-request', 158 'ContentItemSelectionRequest', 159 ]; 160 foreach ($resources as $resource) { 161 $launchable = false; 162 $messages = array(); 163 $tool = new \stdClass(); 164 165 $iconinfo = null; 166 if (is_array($resource->icon_info)) { 167 $iconinfo = $resource->icon_info[0]; 168 } else { 169 $iconinfo = $resource->icon_info; 170 } 171 if (isset($iconinfo) && isset($iconinfo->default_location) && isset($iconinfo->default_location->path)) { 172 $tool->iconpath = $iconinfo->default_location->path; 173 } 174 175 foreach ($resource->message as $message) { 176 if (in_array($message->message_type, $messagetypes)) { 177 $launchable = $launchable || ($message->message_type === 'basic-lti-launch-request'); 178 $messages[$message->message_type] = $message; 179 } 180 } 181 if (!$launchable) { 182 continue; 183 } 184 $tool->name = $resource->resource_name->default_value; 185 $tool->messages = $messages; 186 $tools[] = $tool; 187 } 188 $ok = count($tools) > 0; 189 if (!$ok) { 190 debugging('No launchable messages found in tool proxy'); 191 } 192 } 193 194 // Add tools and custom parameters. 195 if ($ok) { 196 $baseurl = ''; 197 if (isset($toolproxyjson->tool_profile->base_url_choice[0]->default_base_url)) { 198 $baseurl = $toolproxyjson->tool_profile->base_url_choice[0]->default_base_url; 199 } 200 $securebaseurl = ''; 201 if (isset($toolproxyjson->tool_profile->base_url_choice[0]->secure_base_url)) { 202 $securebaseurl = $toolproxyjson->tool_profile->base_url_choice[0]->secure_base_url; 203 } 204 foreach ($tools as $tool) { 205 $messages = $tool->messages; 206 $launchrequest = $messages['basic-lti-launch-request']; 207 $config = new \stdClass(); 208 $config->lti_toolurl = "{$baseurl}{$launchrequest->path}"; 209 $config->lti_typename = $tool->name; 210 $config->lti_coursevisible = 1; 211 $config->lti_forcessl = 0; 212 if (isset($messages['ContentItemSelectionRequest'])) { 213 $contentitemrequest = $messages['ContentItemSelectionRequest']; 214 $config->lti_contentitem = 1; 215 if ($launchrequest->path !== $contentitemrequest->path) { 216 $config->lti_toolurl_ContentItemSelectionRequest = $baseurl . $contentitemrequest->path; 217 } 218 $contentitemcapabilities = implode("\n", $contentitemrequest->enabled_capability); 219 $config->lti_enabledcapability_ContentItemSelectionRequest = $contentitemcapabilities; 220 $contentitemparams = self::lti_extract_parameters($contentitemrequest->parameter); 221 $config->lti_parameter_ContentItemSelectionRequest = $contentitemparams; 222 } 223 224 $type = new \stdClass(); 225 $type->state = LTI_TOOL_STATE_PENDING; 226 $type->ltiversion = LTI_VERSION_2; 227 $type->toolproxyid = $toolproxy->id; 228 // Ensure gradebook column is created. 229 if ($requestsbasicoutcomes && !in_array('BasicOutcome.url', $launchrequest->enabled_capability)) { 230 $launchrequest->enabled_capability[] = 'BasicOutcome.url'; 231 } 232 if ($requestsbasicoutcomes && !in_array('BasicOutcome.sourcedId', $launchrequest->enabled_capability)) { 233 $launchrequest->enabled_capability[] = 'BasicOutcome.sourcedId'; 234 } 235 $type->enabledcapability = implode("\n", $launchrequest->enabled_capability); 236 $type->parameter = self::lti_extract_parameters($launchrequest->parameter); 237 238 if (!empty($tool->iconpath)) { 239 $type->icon = "{$baseurl}{$tool->iconpath}"; 240 if (!empty($securebaseurl)) { 241 $type->secureicon = "{$securebaseurl}{$tool->iconpath}"; 242 } 243 } 244 245 $ok = $ok && (lti_add_type($type, $config) !== false); 246 } 247 if (isset($toolproxyjson->custom)) { 248 lti_set_tool_settings($toolproxyjson->custom, $toolproxy->id); 249 } 250 } 251 252 if (!empty($toolproxy)) { 253 if ($ok) { 254 // If all went OK accept the tool proxy. 255 $toolproxy->state = LTI_TOOL_PROXY_STATE_ACCEPTED; 256 $toolproxy->toolproxy = $response->get_request_data(); 257 $toolproxy->secret = $toolproxyjson->security_contract->shared_secret; 258 $toolproxy->vendorcode = $toolproxyjson->tool_profile->product_instance->product_info->product_family->vendor->code; 259 260 $url = $this->get_endpoint(); 261 $body = <<< EOD 262 { 263 "@context" : "http://purl.imsglobal.org/ctx/lti/v2/ToolProxyId", 264 "@type" : "ToolProxy", 265 "@id" : "{$url}", 266 "tool_proxy_guid" : "{$toolproxy->guid}" 267 } 268 EOD; 269 $response->set_code(201); 270 $response->set_content_type('application/vnd.ims.lti.v2.toolproxy.id+json'); 271 $response->set_body($body); 272 } else { 273 // Otherwise reject the tool proxy. 274 $toolproxy->state = LTI_TOOL_PROXY_STATE_REJECTED; 275 $response->set_code(400); 276 } 277 lti_update_tool_proxy($toolproxy); 278 } else { 279 $response->set_code(400); 280 } 281 } 282 283 /** 284 * Extracts the message parameters from the tool proxy entry 285 * 286 * @param array $parameters Parameter section of a message 287 * 288 * @return String containing parameters 289 */ 290 private static function lti_extract_parameters($parameters) { 291 292 $params = array(); 293 foreach ($parameters as $parameter) { 294 if (isset($parameter->variable)) { 295 $value = '$' . $parameter->variable; 296 } else { 297 $value = $parameter->fixed; 298 if (strlen($value) > 0) { 299 $first = substr($value, 0, 1); 300 if (($first == '$') || ($first == '\\')) { 301 $value = '\\' . $value; 302 } 303 } 304 } 305 $params[] = "{$parameter->name}={$value}"; 306 } 307 308 return implode("\n", $params); 309 310 } 311 312 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body