diff --git a/actions/LtiModule.php b/actions/LtiModule.php index e3e2d0c8..a58ac922 100755 --- a/actions/LtiModule.php +++ b/actions/LtiModule.php @@ -41,8 +41,9 @@ abstract class LtiModule extends tao_actions_CommonModule * * @param string $error error to handle * @param boolean $returnLink + * @param int $httpStatus */ - protected function returnError($error, $returnLink = true) + protected function returnError($error, $returnLink = true, $httpStatus = null) { $error = new \taoLti_models_classes_LtiException($error); $this->returnLtiError($error, $returnLink); diff --git a/actions/class.ToolModule.php b/actions/class.ToolModule.php index 106ec511..865c6636 100755 --- a/actions/class.ToolModule.php +++ b/actions/class.ToolModule.php @@ -42,11 +42,17 @@ public function launch() 'session' => session_id(), 'redirect' => _url('run', null, null, $_GET)))); } else { - $this->returnError(__('You are not authorized to use this system')); + throw new taoLti_models_classes_LtiException( + __('You are not authorized to use this system'), + \oat\taoLti\models\classes\LtiMessages\LtiErrorMessage::ERROR_UNAUTHORIZED + ); } } catch (common_user_auth_AuthFailedException $e) { common_Logger::i($e->getMessage()); - $this->returnError(__('The LTI connection could not be established'), false); + throw new taoLti_models_classes_LtiException( + __('The LTI connection could not be established'), + \oat\taoLti\models\classes\LtiMessages\LtiErrorMessage::ERROR_UNAUTHORIZED + ); } catch (\taoLti_models_classes_LtiException $e) { // In regard of the IMS LTI standard, we have to show a back button that refer to the // launch_presentation_return_url url param. So we have to retrieve this parameter before trying to start @@ -66,7 +72,10 @@ public function launch() $this->returnLtiError($e, false); } catch (tao_models_classes_oauth_Exception $e) { common_Logger::i($e->getMessage()); - $this->returnError(__('The LTI connection could not be established'), false); + throw new taoLti_models_classes_LtiException( + __('The LTI connection could not be established'), + \oat\taoLti\models\classes\LtiMessages\LtiErrorMessage::ERROR_UNAUTHORIZED + ); } } diff --git a/actions/traits/LtiModuleTrait.php b/actions/traits/LtiModuleTrait.php index f78e161d..e4d5d59b 100644 --- a/actions/traits/LtiModuleTrait.php +++ b/actions/traits/LtiModuleTrait.php @@ -39,24 +39,25 @@ trait LtiModuleTrait */ protected function returnLtiError(\taoLti_models_classes_LtiException $error, $returnLink = true) { + // full trace of the error + \common_Logger::e($error->__toString()); + if (tao_helpers_Request::isAjax()) { throw new common_exception_IsAjaxAction(__CLASS__ . '::' . __FUNCTION__); } else { - $session = \common_session_SessionManager::getSession(); - if ($session instanceof \taoLti_models_classes_TaoLtiSession) { - $launchData = $session->getLaunchData(); - // In regard of the IMS LTI standard, we have to show a back button that refer to the - // launch_presentation_return_url url param. So we have to retrieve this parameter before trying to start - // the session - $consumerLabel = $launchData->getToolConsumerName(); - if (!is_null($consumerLabel)) { - $this->setData('consumerLabel', $consumerLabel); - } + $launchData = \taoLti_models_classes_LtiLaunchData::fromRequest(\common_http_Request::currentRequest()); + + if ($launchData->hasVariable(\taoLti_models_classes_LtiLaunchData::LAUNCH_PRESENTATION_RETURN_URL)) { + $flowController = new FlowController(); + $flowController->redirect($this->getLtiReturnUrl($launchData, $error)); + } - if ($launchData->hasVariable(\taoLti_models_classes_LtiLaunchData::LAUNCH_PRESENTATION_RETURN_URL)) { - $flowController = new FlowController(); - $flowController->redirect($this->getLtiReturnUrl($launchData, $error)); - } + // In regard of the IMS LTI standard, we have to show a back button that refer to the + // launch_presentation_return_url url param. So we have to retrieve this parameter before trying to start + // the session + $consumerLabel = $launchData->getToolConsumerName(); + if (!is_null($consumerLabel)) { + $this->setData('consumerLabel', $consumerLabel); } $this->setData('message', $error->getMessage()); diff --git a/manifest.php b/manifest.php index 87a379c9..25fc89e9 100755 --- a/manifest.php +++ b/manifest.php @@ -18,8 +18,9 @@ * * */ -use oat\tao\model\user\TaoRoles; +use oat\tao\model\user\TaoRoles; +use oat\taoLti\scripts\install\InstallServices; /** * @author CRP Henri Tudor - TAO Team - {@link http://www.tao.lu} * @license GPLv2 http://www.opensource.org/licenses/gpl-2.0.php @@ -32,10 +33,10 @@ 'label' => 'LTI library', 'description' => 'TAO LTI library and helpers', 'license' => 'GPL-2.0', - 'version' => '1.11.0', + 'version' => '2.0.0', 'author' => 'Open Assessment Technologies SA', 'requires' => array( - 'tao' => '>=7.45.5' + 'tao' => '>=8.0.1' ), 'models' => array( 'http://www.tao.lu/Ontologies/TAOLTI.rdf', @@ -48,7 +49,10 @@ dirname(__FILE__). '/models/ontology/roledefinition.rdf', dirname(__FILE__). '/models/ontology/ltiroles_person.rdf', dirname(__FILE__). '/models/ontology/ltiroles_membership.rdf' - ) + ), + 'php' => [ + InstallServices::class + ] ), 'update' => 'taoLti_scripts_update_Updater', 'managementRole' => 'http://www.tao.lu/Ontologies/TAOLTI.rdf#LtiManagerRole', diff --git a/models/classes/ExceptionInterpreter.php b/models/classes/ExceptionInterpreter.php new file mode 100644 index 00000000..5fbb9dea --- /dev/null +++ b/models/classes/ExceptionInterpreter.php @@ -0,0 +1,60 @@ + + */ +class ExceptionInterpreter extends ExceptionInterpretor +{ + /** + * @var \taoLti_models_classes_LtiException + */ + protected $exception; + + /** + * set exception to interpet + * @param \Exception $exception + * @return ExceptionInterpretor + */ + public function setException(\Exception $exception){ + parent::setException($exception); + \common_Logger::e($exception->__toString()); + return $this; + } + + /** + * return an instance of ResponseInterface + * @return \oat\tao\model\mvc\error\class + */ + public function getResponse() + { + $response = new LtiReturnResponse; + $response->setServiceLocator($this->getServiceLocator()); + $response->setException($this->exception); + return $response; + } + +} diff --git a/models/classes/LtiReturnResponse.php b/models/classes/LtiReturnResponse.php new file mode 100644 index 00000000..23350ddd --- /dev/null +++ b/models/classes/LtiReturnResponse.php @@ -0,0 +1,66 @@ + + * @property \taoLti_models_classes_LtiException $exception + */ +class LtiReturnResponse extends ResponseAbstract +{ + + public function setHttpCode($code) { + $this->httpCode = 302; + return $this; + } + + public function send() + { + /** @var \taoLti_models_classes_TaoLtiSession $session */ + $session = \common_session_SessionManager::getSession(); + if ($session instanceof \taoLti_models_classes_TaoLtiSession) { + $launchData = $session->getLaunchData(); + $baseUrl = $launchData->getReturnUrl(); + } else { + $request = \common_http_Request::currentRequest(); + $params = $request->getParams(); + isset($params[\taoLti_models_classes_LtiLaunchData::LAUNCH_PRESENTATION_RETURN_URL]) ? + $baseUrl = $params[\taoLti_models_classes_LtiLaunchData::LAUNCH_PRESENTATION_RETURN_URL] : null; + } + + if ($baseUrl !== null) { + $params = $this->exception->getLtiMessage()->getUrlParams(); + $url = $baseUrl . (parse_url($baseUrl, PHP_URL_QUERY) ? '&' : '?') . http_build_query($params); + header(\HTTPToolkit::locationHeader($url)); + } else { + require Template::getTemplate('error/error500.tpl', 'tao'); + } + return; + } + +} diff --git a/models/classes/class.LtiAuthAdapter.php b/models/classes/class.LtiAuthAdapter.php index 02c6746d..8337ab17 100755 --- a/models/classes/class.LtiAuthAdapter.php +++ b/models/classes/class.LtiAuthAdapter.php @@ -19,13 +19,14 @@ * */ +use oat\taoLti\models\classes\LtiMessages\LtiErrorMessage; + /** - * Authentication adapter interface to be implemented by authentication methodes + * Authentication adapter interface to be implemented by authentication methods * * @access public * @author Joel Bout, * @package taoLti - */ class taoLti_models_classes_LtiAuthAdapter implements common_user_auth_Adapter @@ -57,7 +58,7 @@ public function authenticate() { $ltiLaunchData = taoLti_models_classes_LtiLaunchData::fromRequest($this->request); return new taoLti_models_classes_LtiUser($ltiLaunchData); } catch (common_http_InvalidSignatureException $e) { - throw new taoLti_models_classes_LtiException('Invalid LTI signature'); + throw new taoLti_models_classes_LtiException('Invalid LTI signature', LtiErrorMessage::ERROR_UNAUTHORIZED); } } } \ No newline at end of file diff --git a/models/classes/class.LtiException.php b/models/classes/class.LtiException.php index 17c40731..5805956d 100755 --- a/models/classes/class.LtiException.php +++ b/models/classes/class.LtiException.php @@ -23,11 +23,25 @@ class taoLti_models_classes_LtiException extends common_Exception { + /** + * @var string Unique key to determine error in log + */ + private $key; + public function __construct($message = null, $code = 0, Exception $previous = null) { parent::__construct($message, $code, $previous); } + public function getKey() + { + if (!isset($this->key)) { + $this->key = uniqid(); + } + + return $this->key; + } + /** * @var LtiErrorMessage */ @@ -40,10 +54,15 @@ public function getLtiMessage() { if ($this->ltiMessage === null) { $message =__('Error (%s): ', $this->getCode()) . $this->getMessage(); - $log = 'Error(' .$this->getCode() . '): ' . $this->__toString(); + $log = __('Error(%s): [key %s] %s "%s"', $this->getCode(), $this->getKey(), get_class($this), $this->getMessage()); $this->ltiMessage = new LtiErrorMessage($message, $log); } return $this->ltiMessage; } + public function __toString() + { + return '[key ' . $this->getKey() . '] ' . parent::__toString(); + } + } \ No newline at end of file diff --git a/models/classes/class.LtiLaunchData.php b/models/classes/class.LtiLaunchData.php index 5f991141..add572ba 100755 --- a/models/classes/class.LtiLaunchData.php +++ b/models/classes/class.LtiLaunchData.php @@ -19,6 +19,8 @@ * */ +use oat\taoLti\models\classes\LtiMessages\LtiErrorMessage; + class taoLti_models_classes_LtiLaunchData { const OAUTH_CONSUMER_KEY = 'oauth_consumer_key'; @@ -112,7 +114,7 @@ public function getVariable($key) { if (isset($this->variables[$key])) { return $this->variables[$key]; } else { - throw new taoLti_models_classes_LtiException('Undefined LTI variable '.$key); + throw new taoLti_models_classes_LtiException('Undefined LTI variable '.$key, LtiErrorMessage::ERROR_MISSING_PARAMETER); } } diff --git a/models/classes/class.LtiService.php b/models/classes/class.LtiService.php index f53ecd9e..676d79ee 100755 --- a/models/classes/class.LtiService.php +++ b/models/classes/class.LtiService.php @@ -19,6 +19,8 @@ * */ +use oat\taoLti\models\classes\LtiMessages\LtiErrorMessage; + /** * Basic service to handle everything LTI * @@ -48,24 +50,30 @@ public function startLtiSession(common_http_Request $request) { /** * Returns the current LTI session + * @throws \taoLti_models_classes_LtiException * @return taoLti_models_classes_TaoLtiSession */ public function getLtiSession() { $session = common_session_SessionManager::getSession(); if (!$session instanceof taoLti_models_classes_TaoLtiSession) { - throw new taoLti_models_classes_LtiException(__FUNCTION__.' called on a non LTI session'); + throw new taoLti_models_classes_LtiException(__FUNCTION__.' called on a non LTI session', LtiErrorMessage::ERROR_SYSTEM_ERROR); } return $session; } - + + /** + * @param $key + * @return mixed + * @throws taoLti_models_classes_LtiException + */ public function getCredential($key) { $class = new core_kernel_classes_Class(CLASS_LTI_CONSUMER); $instances = $class->searchInstances(array(PROPERTY_OAUTH_KEY => $key), array('like' => false)); if (count($instances) == 0) { - throw new taoLti_models_classes_LtiException('No Credentials for consumer key '.$key); + throw new taoLti_models_classes_LtiException('No Credentials for consumer key '.$key, LtiErrorMessage::ERROR_UNAUTHORIZED); } if (count($instances) > 1) { - throw new taoLti_models_classes_LtiException('Multiple Credentials for consumer key '.$key); + throw new taoLti_models_classes_LtiException('Multiple Credentials for consumer key '.$key, LtiErrorMessage::ERROR_INVALID_PARAMETER); } return current($instances); } @@ -88,11 +96,11 @@ public function getLtiConsumerResource($launchData) * Returns the existing tao User that corresponds to * the LTI request or spawns it * - * @param taoLti_models_classes_LtiLaunchData $ltiContext + * @param taoLti_models_classes_LtiLaunchData $launchData * @throws taoLti_models_classes_LtiException * @return core_kernel_classes_Resource */ - public function findOrSpwanUser(taoLti_models_classes_LtiLaunchData $launchData) { + public function findOrSpawnUser(taoLti_models_classes_LtiLaunchData $launchData) { $taoUser = $this->findUser($launchData); if (is_null($taoUser)) { $taoUser = $this->spawnUser($launchData); @@ -116,7 +124,10 @@ public function findUser(taoLti_models_classes_LtiLaunchData $ltiContext) { 'like' => false )); if (count($instances) > 1) { - throw new taoLti_models_classes_LtiException('Multiple user accounts found for user key \''.$ltiContext->getUserID().'\''); + throw new taoLti_models_classes_LtiException( + 'Multiple user accounts found for user key \''.$ltiContext->getUserID().'\'', + LtiErrorMessage::ERROR_SYSTEM_ERROR + ); } return count($instances) == 1 ? current($instances) : null; } diff --git a/models/classes/class.LtiUser.php b/models/classes/class.LtiUser.php index 1728d660..56449025 100755 --- a/models/classes/class.LtiUser.php +++ b/models/classes/class.LtiUser.php @@ -50,7 +50,7 @@ class taoLti_models_classes_LtiUser public function __construct(taoLti_models_classes_LtiLaunchData $ltiLaunchData) { $this->ltiLaunchData = $ltiLaunchData; - $this->userUri = taoLti_models_classes_LtiService::singleton()->findOrSpwanUser($ltiLaunchData)->getUri(); + $this->userUri = taoLti_models_classes_LtiService::singleton()->findOrSpawnUser($ltiLaunchData)->getUri(); $this->roles = $this->determinTaoRoles(); } diff --git a/scripts/install/InstallServices.php b/scripts/install/InstallServices.php new file mode 100644 index 00000000..29c726fa --- /dev/null +++ b/scripts/install/InstallServices.php @@ -0,0 +1,47 @@ + + */ +class InstallServices extends AbstractAction +{ + + /** + * @param $params + * @return Report + */ + public function __invoke($params) + { + $exceptionInterpreterService = $this->getServiceManager()->get(ExceptionInterpreterService::SERVICE_ID); + $interpreters = $exceptionInterpreterService->getOption(ExceptionInterpreterService::OPTION_INTERPRETERS); + $interpreters[\taoLti_models_classes_LtiException::class] = ExceptionInterpreter::class; + $exceptionInterpreterService->setOption(ExceptionInterpreterService::OPTION_INTERPRETERS, $interpreters); + $this->getServiceManager()->register(ExceptionInterpreterService::SERVICE_ID, $exceptionInterpreterService); + } +} \ No newline at end of file diff --git a/scripts/update/class.Updater.php b/scripts/update/class.Updater.php index c3465435..7f0a06c9 100644 --- a/scripts/update/class.Updater.php +++ b/scripts/update/class.Updater.php @@ -20,6 +20,8 @@ * */ +use oat\tao\model\mvc\error\ExceptionInterpreterService; +use oat\taoLti\models\classes\ExceptionInterpreter; /** * * @author Joel Bout @@ -29,7 +31,7 @@ class taoLti_scripts_update_Updater extends \common_ext_ExtensionUpdater /** * - * @param string $currentVersion + * @param string $initialVersion * @return string $versionUpdatedTo */ public function update($initialVersion) @@ -48,6 +50,16 @@ public function update($initialVersion) OntologyUpdater::syncModels(); $this->setVersion('1.6.0'); } - $this->skip('1.6.0', '1.11.0'); + $this->skip('1.6.0', '1.12.0'); + + if ($this->isVersion('1.12.0')) { + $service = $this->getServiceManager()->get(ExceptionInterpreterService::SERVICE_ID); + $interpreters = $service->getOption(ExceptionInterpreterService::OPTION_INTERPRETERS); + $interpreters[\taoLti_models_classes_LtiException::class] = ExceptionInterpreter::class; + $service->setOption(ExceptionInterpreterService::OPTION_INTERPRETERS, $interpreters); + $this->getServiceManager()->register(ExceptionInterpreterService::SERVICE_ID, $service); + $this->setVersion('1.13.0'); + } + $this->skip('1.13.0', '2.0.0'); } } diff --git a/views/js/controllers.min.js.map b/views/js/controllers.min.js.map index 32f25328..b577ae10 100644 --- a/views/js/controllers.min.js.map +++ b/views/js/controllers.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["routes.js","ltiConsumer.js"],"names":["define","LtiConsumer","actions","call","$","iframeNotifier","start","hide","submit","parent"],"mappings":"AAmBAA,OAAA,8BAAA,WACA,OACAC,aACAC,SACAC,KAAA,8BAMAH,OAAA,iCAAA,SAAA,kBAAA,SAAAI,EAAAC,gBC3BA,OACAC,MAAA,WACAF,EAAA,4BAAAG,OACAH,EAAA,8BAAAI,SAGAH,eAAAI,OAAA,aAGAJ,eAAAI,OAAA,gBAAA,MAGAJ,eAAAI,OAAA","file":"routes.js.map","sourcesContent":["/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2013 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n *\n *\n */\ndefine('taoLti/controller/routes',[],function(){\n return {\n 'LtiConsumer' : {\n 'actions' : {\n 'call' : 'controller/ltiConsumer'\n }\n }\n };\n});\n\n","define('taoLti/controller/ltiConsumer',['jquery', 'iframeNotifier'], function($, iframeNotifier){\n \n return {\n start : function(){\n $(\"#ltiLaunchFormSubmitArea\").hide();\n $(\"form[name='ltiLaunchForm']\").submit();\n\n //ask the parent to hide the loader\n iframeNotifier.parent('unloading');\n \n //set a fix size to the iframe as we are not allowed to check content size\n iframeNotifier.parent('heightchange', [600]);\n \n //ask the parent to stop communicate accross frames because we are going to another domain for new amazing adventures...\n iframeNotifier.parent('shutdown-com');\n }\n };\n});\n\n"]} \ No newline at end of file +{"version":3,"sources":["routes.js","ltiConsumer.js"],"names":["define","LtiConsumer","actions","call","$","iframeNotifier","start","hide","submit","parent"],"mappings":"AAmBAA,OAAA,8BAAA,WACA,OACAC,aACAC,SACAC,KAAA,8BAMAH,OAAA,iCAAA,SAAA,kBAAA,SAAAI,EAAAC,gBC3BA,OACAC,MAAA,WACAF,EAAA,4BAAAG,OACAH,EAAA,8BAAAI,SAGAH,eAAAI,OAAA,aAGAJ,eAAAI,OAAA,gBAAA,MAGAJ,eAAAI,OAAA","file":"routes.js.map","sourcesContent":["/**\r\n * This program is free software; you can redistribute it and/or\r\n * modify it under the terms of the GNU General Public License\r\n * as published by the Free Software Foundation; under version 2\r\n * of the License (non-upgradable).\r\n *\r\n * This program is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program; if not, write to the Free Software\r\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\r\n *\r\n * Copyright (c) 2013 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\r\n *\r\n *\r\n */\r\ndefine('taoLti/controller/routes',[],function(){\r\n return {\r\n 'LtiConsumer' : {\r\n 'actions' : {\r\n 'call' : 'controller/ltiConsumer'\r\n }\r\n }\r\n };\r\n});\r\n\n","define('taoLti/controller/ltiConsumer',['jquery', 'iframeNotifier'], function($, iframeNotifier){\r\n \r\n return {\r\n start : function(){\r\n $(\"#ltiLaunchFormSubmitArea\").hide();\r\n $(\"form[name='ltiLaunchForm']\").submit();\r\n\r\n //ask the parent to hide the loader\r\n iframeNotifier.parent('unloading');\r\n \r\n //set a fix size to the iframe as we are not allowed to check content size\r\n iframeNotifier.parent('heightchange', [600]);\r\n \r\n //ask the parent to stop communicate accross frames because we are going to another domain for new amazing adventures...\r\n iframeNotifier.parent('shutdown-com');\r\n }\r\n };\r\n});\r\n\n"]} \ No newline at end of file