parent
feef7a7af8
commit
9be4f428a1
@ -0,0 +1,54 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\PluginBundle\Entity\ImsLti; |
||||
|
||||
use Doctrine\ORM\Mapping as ORM; |
||||
|
||||
/** |
||||
* Class Deployment. |
||||
* |
||||
* @package Chamilo\PluginBundle\Entity\ImsLti |
||||
* |
||||
* @ORM\Table(name="plugin_ims_lti_deployment") |
||||
* @ORM\Entity() |
||||
*/ |
||||
class Deployment |
||||
{ |
||||
/** |
||||
* @var int |
||||
* |
||||
* @ORM\Column(name="id", type="integer") |
||||
* @ORM\Id() |
||||
* @ORM\GeneratedValue() |
||||
*/ |
||||
protected $id; |
||||
/** |
||||
* @var string |
||||
*/ |
||||
private $name; |
||||
/** |
||||
* @var string |
||||
*/ |
||||
public $deployId; |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getName() |
||||
{ |
||||
return $this->name; |
||||
} |
||||
|
||||
/** |
||||
* @param string $name |
||||
* |
||||
* @return Deployment |
||||
*/ |
||||
public function setName($name) |
||||
{ |
||||
$this->name = $name; |
||||
|
||||
return $this; |
||||
} |
||||
} |
||||
@ -0,0 +1,113 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\PluginBundle\Entity\ImsLti; |
||||
|
||||
use Doctrine\ORM\Mapping as ORM; |
||||
|
||||
/** |
||||
* Class Platform |
||||
* |
||||
* @package Chamilo\PluginBundle\Entity\ImsLti |
||||
* |
||||
* @ORM\Table(name="plugin_ims_lti_platform") |
||||
* @ORM\Entity() |
||||
*/ |
||||
class Platform |
||||
{ |
||||
/** |
||||
* @var int |
||||
* |
||||
* @ORM\Column(name="id", type="integer") |
||||
* @ORM\Id() |
||||
* @ORM\GeneratedValue() |
||||
*/ |
||||
protected $id; |
||||
/** |
||||
* @var string |
||||
* |
||||
* @ORM\Column(name="kid", type="string") |
||||
*/ |
||||
private $kid; |
||||
/** |
||||
* @var string |
||||
* |
||||
* @ORM\Column(name="public_key", type="text") |
||||
*/ |
||||
public $publicKey; |
||||
/** |
||||
* @var string |
||||
* |
||||
* @ORM\Column(name="private_key", type="text") |
||||
*/ |
||||
private $privateKey; |
||||
|
||||
/** |
||||
* Get id. |
||||
* |
||||
* @return int |
||||
*/ |
||||
public function getId() |
||||
{ |
||||
return $this->id; |
||||
} |
||||
|
||||
/** |
||||
* Set id. |
||||
* |
||||
* @param int $id |
||||
* |
||||
* @return Platform |
||||
*/ |
||||
public function setId($id) |
||||
{ |
||||
$this->id = $id; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* |
||||
* Get kid. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getKid() |
||||
{ |
||||
return $this->kid; |
||||
} |
||||
|
||||
/** |
||||
* Set kid. |
||||
* |
||||
* @param string $kid |
||||
*/ |
||||
public function setKid($kid) |
||||
{ |
||||
$this->kid = $kid; |
||||
} |
||||
|
||||
/** |
||||
* Get privateKey. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getPrivateKey() |
||||
{ |
||||
return $this->privateKey; |
||||
} |
||||
|
||||
/** |
||||
* Set privateKey. |
||||
* |
||||
* @param string $privateKey |
||||
* |
||||
* @return Platform |
||||
*/ |
||||
public function setPrivateKey($privateKey) |
||||
{ |
||||
$this->privateKey = $privateKey; |
||||
|
||||
return $this; |
||||
} |
||||
} |
||||
@ -0,0 +1,203 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool; |
||||
use Chamilo\PluginBundle\Entity\ImsLti\Platform; |
||||
use Firebase\JWT\JWT; |
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php'; |
||||
|
||||
//api_protect_course_script(false); |
||||
api_block_anonymous_users(false); |
||||
|
||||
$scope = empty($_REQUEST['scope']) ? '' : trim($_REQUEST['scope']); |
||||
$responseType = empty($_REQUEST['response_type']) ? '' : trim($_REQUEST['response_type']); |
||||
$responseMode = empty($_REQUEST['response_mode']) ? '' : trim($_REQUEST['response_mode']); |
||||
$prompt = empty($_REQUEST['prompt']) ? '' : trim($_REQUEST['prompt']); |
||||
$clientId = empty($_REQUEST['client_id']) ? '' : trim($_REQUEST['client_id']); |
||||
$redirectUri = empty($_REQUEST['redirect_uri']) ? '' : trim($_REQUEST['redirect_uri']); |
||||
$state = empty($_REQUEST['state']) ? '' : trim($_REQUEST['state']); |
||||
$nonce = empty($_REQUEST['nonce']) ? '' : trim($_REQUEST['nonce']); |
||||
$loginHint = empty($_REQUEST['login_hint']) ? '' : trim($_REQUEST['login_hint']); |
||||
$ltiMessageHint = empty($_REQUEST['lti_message_hint']) ? '' : trim($_REQUEST['lti_message_hint']); |
||||
|
||||
$em = Database::getManager(); |
||||
|
||||
try { |
||||
if (empty($scope) || empty($responseType) || empty($clientId) || empty($redirectUri) || empty($loginHint) || |
||||
empty($nonce) |
||||
) { |
||||
throw LtiAuthException::invalidRequest(); |
||||
} |
||||
|
||||
if ($scope !== 'openid') { |
||||
throw LtiAuthException::invalidScope(); |
||||
} |
||||
|
||||
if ($responseType !== 'id_token') { |
||||
throw LtiAuthException::unsupportedResponseType(); |
||||
} |
||||
|
||||
if (empty($responseMode)) { |
||||
throw LtiAuthException::missingResponseMode(); |
||||
} |
||||
|
||||
if ($responseMode !== 'form_post') { |
||||
throw LtiAuthException::invalidRespondeMode(); |
||||
} |
||||
|
||||
if ($prompt !== 'none') { |
||||
throw LtiAuthException::invalidPrompt(); |
||||
} |
||||
|
||||
$ltiToolLogin = ChamiloSession::read('lti_tool_login'); |
||||
|
||||
if ($ltiToolLogin != $ltiMessageHint) { |
||||
throw LtiAuthException::invalidRequest(); |
||||
} |
||||
|
||||
/** @var ImsLtiTool $tool */ |
||||
$tool = $em |
||||
->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $ltiToolLogin); |
||||
|
||||
if (empty($tool)) { |
||||
throw LtiAuthException::invalidRequest(); |
||||
} |
||||
|
||||
if ($tool->getClientId() != $clientId) { |
||||
throw LtiAuthException::unauthorizedClient(); |
||||
} |
||||
|
||||
$user = api_get_user_entity(api_get_user_id()); |
||||
|
||||
if ($user->getId() != $loginHint) { |
||||
throw LtiAuthException::accessDenied(); |
||||
} |
||||
|
||||
if ($redirectUri !== $tool->getRedirectUrl()) { |
||||
throw LtiAuthException::unregisteredRedirectUri(); |
||||
} |
||||
|
||||
/** @var Platform|null $platform */ |
||||
$platform = $em |
||||
->getRepository('ChamiloPluginBundle:ImsLti\Platform') |
||||
->findOneBy([]); |
||||
$session = api_get_session_entity(api_get_session_id()); |
||||
$course = api_get_course_entity(api_get_course_int_id()); |
||||
|
||||
$toolUserId = ImsLtiPlugin::generateToolUserId($user->getId()); |
||||
$platformDomain = str_replace(['https://', 'http://'], '', api_get_setting('InstitutionUrl')); |
||||
|
||||
$jwtContent = []; |
||||
$jwtContent['iss'] = ImsLtiPlugin::getIssuerUrl(); |
||||
$jwtContent['sub'] = (string) $user->getId(); |
||||
$jwtContent['aud'] = $tool->getClientId(); |
||||
$jwtContent['iat'] = time(); |
||||
$jwtContent['exp'] = time() + 60; |
||||
$jwtContent['nonce'] = md5(microtime().mt_rand()); |
||||
|
||||
// User info |
||||
if ($tool->isSharingName()) { |
||||
$jwtContent['name'] = $user->getFullname(); |
||||
$jwtContent['given_name'] = $user->getFirstname(); |
||||
$jwtContent['family_name'] = $user->getLastname(); |
||||
} |
||||
|
||||
if ($tool->isSharingPicture()) { |
||||
$jwtContent['picture'] = UserManager::getUserPicture($user->getId()); |
||||
} |
||||
|
||||
if ($tool->isSharingEmail()) { |
||||
$jwtContent['email'] = $user->getEmail(); |
||||
} |
||||
|
||||
// Course (context) info |
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/context'] = [ |
||||
'id' => (string) $course->getId(), |
||||
'title' => $course->getTitle(), |
||||
'label' => $course->getCode(), |
||||
'type' => ['http://purl.imsglobal.org/vocab/lis/v2/course#CourseSection'], |
||||
]; |
||||
|
||||
// Deployment info |
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/deployment_id'] = $session |
||||
? "{$session->getId()}-{$course->getId()}-{$tool->getId()}" |
||||
: "{$course->getId()}-{$tool->getId()}"; |
||||
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/target_link_uri'] = $tool->getLaunchUrl(); |
||||
|
||||
// Resource link |
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/resource_link'] = [ |
||||
'id' => (string) $tool->getId(), |
||||
'title' => $tool->getName(), |
||||
'description' => $tool->getDescription(), |
||||
]; |
||||
|
||||
// Platform info |
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/tool_platform'] = [ |
||||
'guid' => $platformDomain, |
||||
'contact_email' => api_get_setting('emailAdministrator'), |
||||
'name' => api_get_setting('siteName'), |
||||
'family_code' => 'Chamilo LMS', |
||||
'version' => api_get_version(), |
||||
]; |
||||
|
||||
// Launch info |
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/launch_presentation'] = [ |
||||
'locale' => api_get_language_isocode($user->getLanguage()), |
||||
'document_target' => 'iframe', |
||||
//'height' => 320, |
||||
//'wdith' => 240, |
||||
//'return_url' => api_get_course_url(), |
||||
]; |
||||
|
||||
// LIS info |
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/lis'] = [ |
||||
'person_sourcedid' => "$platformDomain:$toolUserId", |
||||
'course_offering_sourcedid' => "$platformDomain:{$course->getId()}" |
||||
.($session ? ":{$session->getId()}" : ''), |
||||
]; |
||||
|
||||
// LTI info |
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/version'] = '1.3.0'; |
||||
|
||||
// Roles info |
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/roles'] = ImsLtiPlugin::getRoles($user); |
||||
|
||||
// Message type info |
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/message_type'] = 'LtiResourceLinkRequest'; |
||||
|
||||
// Custom params info |
||||
$customParams = $tool->getCustomParamsAsArray(); |
||||
|
||||
if (!empty($customParams)) { |
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/custom'] = $customParams; |
||||
} |
||||
|
||||
// Sign |
||||
$jwt = JWT::encode( |
||||
$jwtContent, |
||||
$platform->getPrivateKey(), |
||||
'RS256', |
||||
$platform->getKid() |
||||
); |
||||
|
||||
$params = [ |
||||
'id_token' => $jwt, |
||||
'state' => $state, |
||||
]; |
||||
} catch (LtiAuthException $authException) { |
||||
$params = [ |
||||
'error' => $authException->getType(), |
||||
'error_description' => $authException->getMessage(), |
||||
]; |
||||
} |
||||
?> |
||||
<!DOCTYPE html> |
||||
<html> |
||||
<form action="<?php echo $tool->getLaunchUrl() ?>" name="ltiLaunchForm" method="post">
|
||||
<input type="hidden" name="id_token" value="<?php echo $jwt ?>">
|
||||
<input type="hidden" name="state" value="<?php echo $state ?>">
|
||||
</form> |
||||
<script>document.ltiLaunchForm.submit();</script> |
||||
</html> |
||||
@ -0,0 +1,47 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\Platform; |
||||
use Firebase\JWT\JWT; |
||||
use phpseclib\Crypt\RSA; |
||||
|
||||
$cidReset = true; |
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php'; |
||||
|
||||
$plugin = ImsLtiPlugin::create(); |
||||
|
||||
if ($plugin->get('enabled') !== 'true') { |
||||
die; |
||||
} |
||||
|
||||
/** @var Platform $platform */ |
||||
$platform = Database::getManager() |
||||
->getRepository('ChamiloPluginBundle:ImsLti\Platform') |
||||
->findOneBy([]); |
||||
|
||||
if (!$platform) { |
||||
exit; |
||||
} |
||||
|
||||
$jwks = []; |
||||
|
||||
$key = new RSA(); |
||||
$key->setHash('sha256'); |
||||
$key->loadKey($platform->getPrivateKey()); |
||||
$key->setPublicKey(false, RSA::PUBLIC_FORMAT_PKCS8); |
||||
|
||||
if ($key->publicExponent) { |
||||
$jwks = [ |
||||
'kty' => 'RSA', |
||||
'alg' => 'RS256', |
||||
'use' => 'sig', |
||||
'e' => JWT::urlsafeB64Encode($key->publicExponent->toBytes()), |
||||
'n' => JWT::urlsafeB64Encode($key->modulus->toBytes()), |
||||
'kid' => $platform->getKid(), |
||||
]; |
||||
} |
||||
|
||||
header('Content-Type: application/json'); |
||||
|
||||
echo json_encode(['keys' => [$jwks]]); |
||||
@ -0,0 +1,43 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool; |
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php'; |
||||
|
||||
api_protect_course_script(false); |
||||
api_block_anonymous_users(false); |
||||
|
||||
$em = Database::getManager(); |
||||
|
||||
/** @var ImsLtiTool $tool */ |
||||
$tool = isset($_GET['id']) |
||||
? $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $_GET['id']) |
||||
: null; |
||||
|
||||
if (!$tool) { |
||||
api_not_allowed(true); |
||||
} |
||||
|
||||
$user = api_get_user_entity(api_get_user_id()); |
||||
|
||||
ChamiloSession::write('lti_tool_login', $tool->getId()); |
||||
|
||||
$params = [ |
||||
'iss' => ImsLtiPlugin::getIssuerUrl(), |
||||
'target_link_uri' => $tool->getLaunchUrl(), |
||||
'login_hint' => $user->getId(), |
||||
'lti_message_hint' => $tool->getId(), |
||||
]; |
||||
?> |
||||
<!DOCTYPE html> |
||||
<body> |
||||
<form action="<?php echo $tool->getLoginUrl() ?>" method="post" name="lti_1p3_login" id="lti_1p3_login"
|
||||
enctype="application/x-www-form-urlencoded" class="form-horizontal"> |
||||
<?php foreach ($params as $name => $value) { ?> |
||||
<input type="hidden" name="<?php echo $name ?>" value="<?php echo $value ?>">
|
||||
<?php } ?> |
||||
</form> |
||||
|
||||
<script>document.lti_1p3_login.submit();</script> |
||||
</body> |
||||
@ -0,0 +1,39 @@ |
||||
<?php |
||||
/* For license terms, see /license.txt */ |
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\Platform; |
||||
|
||||
$cidReset = true; |
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php'; |
||||
|
||||
api_protect_admin_script(); |
||||
|
||||
$plugin = ImsLtiPlugin::create(); |
||||
|
||||
if ($plugin->get('enabled') !== 'true') { |
||||
api_not_allowed(true); |
||||
} |
||||
|
||||
/** @var Platform $platform */ |
||||
$platform = Database::getManager() |
||||
->getRepository('ChamiloPluginBundle:ImsLti\Platform') |
||||
->findOneBy([]); |
||||
|
||||
$table = new HTML_Table(['class' => 'table table-striped']); |
||||
$table->setHeaderContents(0, 0, $plugin->get_lang('KeyId')); |
||||
$table->setHeaderContents(0, 1, $plugin->get_lang('PublicKey')); |
||||
$table->setHeaderContents(0, 2, $plugin->get_lang('PrivateKey')); |
||||
$table->setCellContents(1, 0, $platform ? $platform->getKid() : ''); |
||||
$table->setCellContents(1, 1, $platform ? nl2br($platform->publicKey) : ''); |
||||
$table->setCellContents(1, 2, $platform ? nl2br($platform->getPrivateKey()) : ''); |
||||
$table->updateCellAttributes(1, 1, ['style' => 'font-family: monospace; font-size: 10px;']); |
||||
$table->updateCellAttributes(1, 2, ['style' => 'font-family: monospace; font-size: 10px;']); |
||||
|
||||
$interbreadcrumb[] = ['url' => api_get_path(WEB_CODE_PATH).'admin/index.php', 'name' => get_lang('PlatformAdmin')]; |
||||
$interbreadcrumb[] = ['url' => api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php', 'name' => $plugin->get_title()]; |
||||
|
||||
$template = new Template($plugin->get_lang('ConfigurePlatform')); |
||||
$template->assign('header', $plugin->get_lang('ConfigurePlatform')); |
||||
$template->assign('content', $table->toHtml()); |
||||
$template->display_one_col_template(); |
||||
@ -0,0 +1,168 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
/** |
||||
* Class LtiAuthException. |
||||
*/ |
||||
class LtiAuthException extends Exception |
||||
{ |
||||
const INVALID_REQUEST = 1; |
||||
const INVALID_SCOPE = 2; |
||||
const UNSUPPORTED_RESPONSE_TYPE = 3; |
||||
const UNAUTHORIZED_CLIENT = 4; |
||||
const ACCESS_DENIED = 5; |
||||
const UNREGISTERED_REDIRECT_URI = 6; |
||||
const INVALID_RESPONSE_MODE = 7; |
||||
const MISSING_RESPONSE_MODE = 8; |
||||
const INVALID_PROMPT = 9; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
private $type; |
||||
|
||||
/** |
||||
* LtiAuthException constructor. |
||||
* |
||||
* @param int $code |
||||
* @param Throwable|null $previous |
||||
*/ |
||||
public function __construct($code = 0, Throwable $previous = null) |
||||
{ |
||||
switch ($code) { |
||||
case self::INVALID_SCOPE: |
||||
$this->type = 'invalid_scope'; |
||||
$message = 'Invalid scope'; |
||||
break; |
||||
case self::UNSUPPORTED_RESPONSE_TYPE: |
||||
$this->type = 'unsupported_response_type'; |
||||
$message = 'Unsupported responde type'; |
||||
break; |
||||
case self::UNAUTHORIZED_CLIENT: |
||||
$this->type = 'unauthorized_client'; |
||||
$message = 'Unauthorized client'; |
||||
break; |
||||
case self::ACCESS_DENIED: |
||||
$this->type = 'access_denied'; |
||||
$message = 'Access denied'; |
||||
break; |
||||
case self::UNREGISTERED_REDIRECT_URI: |
||||
$message = 'Unregistered redirect_uri'; |
||||
break; |
||||
case self::INVALID_RESPONSE_MODE: |
||||
$message = 'Invalid response_mode'; |
||||
break; |
||||
case self::MISSING_RESPONSE_MODE: |
||||
$message = 'Missing response_mode'; |
||||
break; |
||||
case self::INVALID_PROMPT: |
||||
$message = 'Invalid prompt'; |
||||
break; |
||||
case self::INVALID_REQUEST: |
||||
default: |
||||
$this->type = 'invalid_request'; |
||||
$message = 'Invalid request'; |
||||
break; |
||||
} |
||||
|
||||
parent::__construct($message, $code, $previous); |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getType() |
||||
{ |
||||
return $this->type; |
||||
} |
||||
|
||||
/** |
||||
* @param Throwable|null $previous |
||||
* |
||||
* @return LtiAuthException |
||||
*/ |
||||
public static function invalidRequest(Throwable $previous = null) |
||||
{ |
||||
return new self(self::INVALID_REQUEST, $previous); |
||||
} |
||||
|
||||
/** |
||||
* @param Throwable|null $previous |
||||
* |
||||
* @return LtiAuthException |
||||
*/ |
||||
public static function invalidScope(Throwable $previous = null) |
||||
{ |
||||
return new self(self::INVALID_SCOPE, $previous); |
||||
} |
||||
|
||||
/** |
||||
* @param Throwable|null $previous |
||||
* |
||||
* @return LtiAuthException |
||||
*/ |
||||
public static function unsupportedResponseType(Throwable $previous = null) |
||||
{ |
||||
return new self(self::UNSUPPORTED_RESPONSE_TYPE, $previous); |
||||
} |
||||
|
||||
/** |
||||
* @param Throwable|null $previous |
||||
* |
||||
* @return LtiAuthException |
||||
*/ |
||||
public static function unauthorizedClient(Throwable $previous = null) |
||||
{ |
||||
return new self(self::UNAUTHORIZED_CLIENT, $previous); |
||||
} |
||||
|
||||
/** |
||||
* @param Throwable|null $previous |
||||
* |
||||
* @return LtiAuthException |
||||
*/ |
||||
public static function accessDenied(Throwable $previous = null) |
||||
{ |
||||
return new self(self::ACCESS_DENIED, $previous); |
||||
} |
||||
|
||||
/** |
||||
* @param Throwable|null $previous |
||||
* |
||||
* @return LtiAuthException |
||||
*/ |
||||
public static function unregisteredRedirectUri(Throwable $previous = null) |
||||
{ |
||||
return new self(self::UNREGISTERED_REDIRECT_URI, $previous); |
||||
} |
||||
|
||||
/** |
||||
* @param Throwable|null $previous |
||||
* |
||||
* @return LtiAuthException |
||||
*/ |
||||
public static function invalidRespondeMode(Throwable $previous = null) |
||||
{ |
||||
return new self(self::INVALID_RESPONSE_MODE, $previous); |
||||
} |
||||
|
||||
/** |
||||
* @param Throwable|null $previous |
||||
* |
||||
* @return LtiAuthException |
||||
*/ |
||||
public static function missingResponseMode(Throwable $previous = null) |
||||
{ |
||||
return new self(self::MISSING_RESPONSE_MODE, $previous); |
||||
} |
||||
|
||||
/** |
||||
* @param Throwable|null $previous |
||||
* |
||||
* @return LtiAuthException |
||||
*/ |
||||
public static function invalidPrompt(Throwable $previous = null) |
||||
{ |
||||
return new self(self::INVALID_PROMPT, $previous); |
||||
} |
||||
} |
||||
@ -0,0 +1,90 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool; |
||||
use Firebase\JWT\JWT; |
||||
|
||||
$cidReset = true; |
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php'; |
||||
|
||||
$plugin = ImsLtiPlugin::create(); |
||||
|
||||
try { |
||||
if ($plugin->get('enabled') !== 'true') { |
||||
throw new Exception('unsupported'); |
||||
} |
||||
|
||||
$contenttype = isset($_SERVER['CONTENT_TYPE']) ? explode(';', $_SERVER['CONTENT_TYPE'], 2)[0] : ''; |
||||
|
||||
if ('POST' !== $_SERVER['REQUEST_METHOD'] || $contenttype !== 'application/x-www-form-urlencoded') { |
||||
throw new Exception('invalid_request'); |
||||
} |
||||
|
||||
$clientAssertion = empty($_POST['client_assertion']) ? $_POST['client_assertion'] : ''; |
||||
$clientAssertionType = empty($_POST['client_assertion_type']) ? $_POST['client_assertion_type'] : ''; |
||||
$grantType = empty($_POST['grant_type']) ? $_POST['grant_type'] : ''; |
||||
$scope = empty($_POST['scope']) ? $_POST['scope'] : ''; |
||||
|
||||
if (empty($clientAssertion) || empty($clientAssertionType) || empty($grantType) || empty($scope)) { |
||||
throw new Exception('invalid_request'); |
||||
} |
||||
|
||||
if ('urn:ietf:params:oauth:client-assertion-type:jwt-bearer' !== $clientAssertionType || |
||||
$grantType !== 'client_credentials' |
||||
) { |
||||
throw new Exception('unsupported_grant_type'); |
||||
} |
||||
|
||||
$parts = explode('.', $clientAssertion); |
||||
|
||||
if (count($parts) !== 3) { |
||||
throw new Exception('invalid_request'); |
||||
} |
||||
|
||||
$payload = JWT::urlsafeB64Decode($parts[1]); |
||||
$claims = json_decode($payload, true); |
||||
|
||||
if (empty($claims) || empty($claims['sub'])) { |
||||
throw new Exception('invalid_request'); |
||||
} |
||||
|
||||
$em = Database::getManager(); |
||||
|
||||
/** @var ImsLtiTool $tool */ |
||||
$tool = $em |
||||
->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool') |
||||
->findOneBy(['clientId' => $claims['sub']]); |
||||
|
||||
if (!$tool || empty($tool->publicKey)) { |
||||
throw new Exception('invalid_client'); |
||||
} |
||||
|
||||
try { |
||||
$jwt = JWT::decode($clientAssertion, $tool->publicKey, ['RS256']); |
||||
} catch (Exception $exception) { |
||||
throw new Exception('invalid_client'); |
||||
} |
||||
|
||||
$requestedScopes = explode(' ', $scope); |
||||
$scopes = $requestedScopes; |
||||
|
||||
if (empty($scopes)) { |
||||
throw new Exception('invalid_scope'); |
||||
} |
||||
|
||||
$json = [ |
||||
'access_token' => '', |
||||
'token_type' => 'Bearer', |
||||
'expires_in' => '', |
||||
'scope' => implode(' ', $scopes), |
||||
]; |
||||
} catch (Exception $exception) { |
||||
header("HTTP/1.0 400 Bad Request"); |
||||
|
||||
$json = ['error' => $exception->getMessage()]; |
||||
} |
||||
|
||||
header('Content-Type: application/json'); |
||||
|
||||
echo json_encode($json); |
||||
Loading…
Reference in new issue