From 1f8c92fe9bdfbaab91892da365c9ca79fdfd7c4e Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 23 Dec 2022 17:41:20 -0500 Subject: [PATCH] Plugin - Allow to use jwks url in client LTI - refs BT#20451 --- plugin/ims_lti/Entity/ImsLtiTool.php | 33 +++++++++++++++++++++++++++- plugin/ims_lti/ImsLtiPlugin.php | 29 ++++++++++++++++++++++++ plugin/ims_lti/create.php | 1 + plugin/ims_lti/edit.php | 1 + plugin/ims_lti/lang/english.php | 4 ++++ plugin/ims_lti/lang/french.php | 4 ++++ plugin/ims_lti/lang/spanish.php | 4 ++++ plugin/ims_lti/src/Form/FrmAdd.php | 20 +++++++++++++++++ plugin/ims_lti/src/Form/FrmEdit.php | 17 +++++++++----- plugin/ims_lti/src/ImsLti.php | 2 ++ plugin/ims_lti/start.php | 3 ++- 11 files changed, 110 insertions(+), 8 deletions(-) diff --git a/plugin/ims_lti/Entity/ImsLtiTool.php b/plugin/ims_lti/Entity/ImsLtiTool.php index 8143f7567b..6a5d6e1fe1 100644 --- a/plugin/ims_lti/Entity/ImsLtiTool.php +++ b/plugin/ims_lti/Entity/ImsLtiTool.php @@ -134,6 +134,13 @@ class ImsLtiTool */ private $redirectUrl; + /** + * @var string|null + * + * @ORM\Column(name="jwks_url", type="string", nullable=true) + */ + private $jwksUrl; + /** * @var array * @@ -183,7 +190,7 @@ class ImsLtiTool $this->consumerKey = null; $this->sharedSecret = null; $this->lineItems = new ArrayCollection(); - $this->version = \ImsLti::V_1P1; + $this->version = \ImsLti::V_1P3; $this->launchPresentation = [ 'document_target' => 'iframe', ]; @@ -626,6 +633,30 @@ class ImsLtiTool return $this; } + /** + * Get jwksUrl. + * + * @return string|null + */ + public function getJwksUrl() + { + return $this->jwksUrl; + } + + /** + * Set jwksUrl. + * + * @param string|null $jwksUrl + * + * @return ImsLtiTool + */ + public function setJwksUrl($jwksUrl) + { + $this->jwksUrl = $jwksUrl; + + return $this; + } + /** * Get clientId. * diff --git a/plugin/ims_lti/ImsLtiPlugin.php b/plugin/ims_lti/ImsLtiPlugin.php index 93687192e9..b71d9be696 100644 --- a/plugin/ims_lti/ImsLtiPlugin.php +++ b/plugin/ims_lti/ImsLtiPlugin.php @@ -11,6 +11,7 @@ use Chamilo\PluginBundle\Entity\ImsLti\Platform; use Chamilo\PluginBundle\Entity\ImsLti\Token; use Chamilo\UserBundle\Entity\User; use Doctrine\ORM\Tools\SchemaTool; +use Firebase\JWT\JWK; /** * Description of MsiLti. @@ -583,6 +584,34 @@ class ImsLtiPlugin extends Plugin }); } + /** + * It gets the public key from jwks or rsa keys. + * + * @param ImsLtiTool $tool + * + * @return mixed|string|null + */ + public static function getToolPublicKey(ImsLtiTool $tool) + { + $publicKey = ''; + if (!empty($tool->getJwksUrl())) { + $publicKeySet = json_decode(file_get_contents($tool->getJwksUrl()), true); + $pk = []; + foreach ($publicKeySet['keys'] as $key) { + $pk = openssl_pkey_get_details( + JWK::parseKeySet(['keys' => [$key]])[$key['kid']] + ); + } + if (!empty($pk)) { + $publicKey = $pk['key']; + } + } else { + $publicKey = $tool->publicKey; + }; + + return $publicKey; + } + /** * @return string */ diff --git a/plugin/ims_lti/create.php b/plugin/ims_lti/create.php index 51d90bcd97..7b6dbc967c 100644 --- a/plugin/ims_lti/create.php +++ b/plugin/ims_lti/create.php @@ -57,6 +57,7 @@ if ($form->validate()) { 'ags' => $formValues['1p3_ags'], ] ) + ->setJwksUrl($formValues['jwks_url']) ->publicKey = $formValues['public_key']; } else { if (empty($formValues['consumer_key']) && empty($formValues['shared_secret'])) { diff --git a/plugin/ims_lti/edit.php b/plugin/ims_lti/edit.php index dd6136a8b4..7e0f709fef 100644 --- a/plugin/ims_lti/edit.php +++ b/plugin/ims_lti/edit.php @@ -73,6 +73,7 @@ if ($form->validate()) { 'nrps' => $formValues['1p3_nrps'], ] ) + ->setJwksUrl($formValues['jwks_url']) ->publicKey = $formValues['public_key']; } diff --git a/plugin/ims_lti/lang/english.php b/plugin/ims_lti/lang/english.php index d57247c65e..db88b6ba3c 100644 --- a/plugin/ims_lti/lang/english.php +++ b/plugin/ims_lti/lang/english.php @@ -49,6 +49,10 @@ $strings['PublicKey'] = 'Public key'; $strings['PrivateKey'] = 'Private key'; $strings['PlatformDateUpdated'] = 'Platform data updated'; $strings['LtiVersion'] = 'LTI Version'; +$strings['PublicKeyType'] = 'Public key type'; +$strings['KeySetUrl'] = 'Keyset URL'; +$strings['RsaKey'] = 'RSA key'; +$strings['PublicKeyset'] = 'Jwks URL'; $strings['LoginUrl'] = 'Login URL'; $strings['RedirectUrl'] = 'Redirect URL'; $strings['AssigmentAndGradesService'] = 'Assigment and Grades Service'; diff --git a/plugin/ims_lti/lang/french.php b/plugin/ims_lti/lang/french.php index a70176e790..1039ec18db 100644 --- a/plugin/ims_lti/lang/french.php +++ b/plugin/ims_lti/lang/french.php @@ -49,6 +49,10 @@ $strings['PublicKey'] = 'Clef publique'; $strings['PrivateKey'] = 'Clef privée'; $strings['PlatformDateUpdated'] = 'Données de la plateforme mises à jour'; $strings['LtiVersion'] = 'Version LTI'; +$strings['PublicKeyType'] = 'Type de clé publique'; +$strings['KeySetUrl'] = 'URL du jeu de clés'; +$strings['RsaKey'] = 'Clé RSA'; +$strings['PublicKeyset'] = 'URL de Jwks'; $strings['LoginUrl'] = 'URL de connexion'; $strings['RedirectUrl'] = 'URL de redirection'; $strings['AssigmentAndGradesService'] = 'Service de travaux et notes'; diff --git a/plugin/ims_lti/lang/spanish.php b/plugin/ims_lti/lang/spanish.php index de5154eb9c..84f6b030bc 100644 --- a/plugin/ims_lti/lang/spanish.php +++ b/plugin/ims_lti/lang/spanish.php @@ -49,6 +49,10 @@ $strings['PublicKey'] = 'Llave pública'; $strings['PrivateKey'] = 'Llave privada'; $strings['PlatformDateUpdated'] = 'Datos de la plataforma actualizados.'; $strings['LtiVersion'] = 'Versión LTI'; +$strings['PublicKeyType'] = 'Tipo de clave pública'; +$strings['KeySetUrl'] = 'KeySet URL'; +$strings['RsaKey'] = 'Clave RSA'; +$strings['PublicKeyset'] = 'URL de Jwks'; $strings['LoginUrl'] = 'URL de Login'; $strings['RedirectUrl'] = 'URL de redirección'; $strings['AssigmentAndGradesService'] = 'Servicio de tareas y notas'; diff --git a/plugin/ims_lti/src/Form/FrmAdd.php b/plugin/ims_lti/src/Form/FrmAdd.php index 0eba3e3a32..bfc1397446 100644 --- a/plugin/ims_lti/src/Form/FrmAdd.php +++ b/plugin/ims_lti/src/Form/FrmAdd.php @@ -73,11 +73,24 @@ class FrmAdd extends FormValidator $this->addText('shared_secret', $plugin->get_lang('SharedSecret'), false); $this->addHtml(''); $this->addHtml('
'); + $this->addRadio( + 'public_key_type', + $plugin->get_lang('PublicKeyType'), + [ + ImsLti::LTI_JWK_KEYSET => $plugin->get_lang('KeySetUrl'), + ImsLti::LTI_RSA_KEY => $plugin->get_lang('RsaKey'), + ] + ); + $this->addHtml('
'); + $this->addUrl('jwks_url', $plugin->get_lang('PublicKeyset'), false); + $this->addHtml('
'); + $this->addHtml(''); $this->addUrl('login_url', $plugin->get_lang('LoginUrl'), false); $this->addUrl('redirect_url', $plugin->get_lang('RedirectUrl'), false); $this->addHtml('
'); @@ -178,6 +191,7 @@ class FrmAdd extends FormValidator { $defaults = []; $defaults['version'] = ImsLti::V_1P3; + $defaults['public_key_type'] = ImsLti::LTI_JWK_KEYSET; if ($this->baseTool) { $defaults['name'] = $this->baseTool->getName(); @@ -209,6 +223,12 @@ class FrmAdd extends FormValidator \$('[name=\"version\"]').on('change', function () { $('.".ImsLti::V_1P1.", .".ImsLti::V_1P3."').hide(); + $('.' + this.value).show(); + }) + \$('[name=\"public_key_type\"]').on('change', function () { + $('.".ImsLti::LTI_JWK_KEYSET.", .".ImsLti::LTI_RSA_KEY."').hide(); + $('[name=\"public_key\"], [name=\"jwks_url\"]').val(''); + $('.' + this.value).show(); }) }); diff --git a/plugin/ims_lti/src/Form/FrmEdit.php b/plugin/ims_lti/src/Form/FrmEdit.php index c63101c561..541efa71de 100644 --- a/plugin/ims_lti/src/Form/FrmEdit.php +++ b/plugin/ims_lti/src/Form/FrmEdit.php @@ -82,12 +82,16 @@ class FrmEdit extends FormValidator } elseif ($this->tool->getVersion() === ImsLti::V_1P3) { $this->addText('client_id', $plugin->get_lang('ClientId'), true); $this->freeze(['client_id']); - $this->addTextarea( - 'public_key', - $plugin->get_lang('PublicKey'), - ['style' => 'font-family: monospace;', 'rows' => 5], - true - ); + if (!empty($this->tool->getJwksUrl())) { + $this->addUrl('jwks_url', $plugin->get_lang('PublicKeyset')); + } else { + $this->addTextarea( + 'public_key', + $plugin->get_lang('PublicKey'), + ['style' => 'font-family: monospace;', 'rows' => 5], + true + ); + } $this->addUrl('login_url', $plugin->get_lang('LoginUrl')); $this->addUrl('redirect_url', $plugin->get_lang('RedirectUrl')); } @@ -207,6 +211,7 @@ class FrmEdit extends FormValidator 'version' => $this->tool->getVersion(), 'client_id' => $this->tool->getClientId(), 'public_key' => $this->tool->publicKey, + 'jwks_url' => $this->tool->getJwksUrl(), 'login_url' => $this->tool->getLoginUrl(), 'redirect_url' => $this->tool->getRedirectUrl(), '1p3_ags' => $advServices['ags'], diff --git a/plugin/ims_lti/src/ImsLti.php b/plugin/ims_lti/src/ImsLti.php index 4362a7e0b5..93d9395b40 100644 --- a/plugin/ims_lti/src/ImsLti.php +++ b/plugin/ims_lti/src/ImsLti.php @@ -13,6 +13,8 @@ class ImsLti { const V_1P1 = 'lti1p1'; const V_1P3 = 'lti1p3'; + const LTI_RSA_KEY = 'rsa_key'; + const LTI_JWK_KEYSET = 'jwk_keyset'; /** * @param Session|null $session Optional. diff --git a/plugin/ims_lti/start.php b/plugin/ims_lti/start.php index a609792ecd..dd0efdae72 100644 --- a/plugin/ims_lti/start.php +++ b/plugin/ims_lti/start.php @@ -22,8 +22,9 @@ if (!$tool) { $imsLtiPlugin = ImsLtiPlugin::create(); $pageTitle = Security::remove_XSS($tool->getName()); +$publicKey = ImsLtiPlugin::getToolPublicKey($tool); -$is1p3 = !empty($tool->publicKey) && !empty($tool->getClientId()) && +$is1p3 = !empty($publicKey) && !empty($tool->getClientId()) && !empty($tool->getLoginUrl()) && !empty($tool->getRedirectUrl()); if ($is1p3) {