From 0d6186c0a623a7b8ca35f53d1df30033499e59f3 Mon Sep 17 00:00:00 2001 From: mjansen Date: Tue, 31 Oct 2023 10:00:13 +0100 Subject: [PATCH] Mail: Validate `Mustache` templates See: https://mantis.ilias.de/view.php?id=38409 --- Services/Mail/classes/Service/MailService.php | 5 ++- .../TemplateMessageSyntaxException.php | 27 +++++++++++++++ .../TemplateSubjectSyntaxException.php | 27 +++++++++++++++ .../Mail/classes/class.ilMailTemplateGUI.php | 10 ++++++ .../classes/class.ilMailTemplateService.php | 33 +++++++++++++++++-- Services/Mail/classes/class.ilObjMailGUI.php | 29 ++++++++++++++++ ...terface.ilMailTemplateServiceInterface.php | 6 ++++ .../Mail/test/ilMailTemplateServiceTest.php | 6 ++-- lang/ilias_de.lang | 1 + lang/ilias_en.lang | 1 + 10 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 Services/Mail/classes/Templates/TemplateMessageSyntaxException.php create mode 100644 Services/Mail/classes/Templates/TemplateSubjectSyntaxException.php diff --git a/Services/Mail/classes/Service/MailService.php b/Services/Mail/classes/Service/MailService.php index 7b5b55a6cb1b..1b0554c9e94e 100644 --- a/Services/Mail/classes/Service/MailService.php +++ b/Services/Mail/classes/Service/MailService.php @@ -35,7 +35,10 @@ public function __construct(protected Container $dic) { if (!isset($this->dic[ilMailTemplateServiceInterface::class])) { $this->dic[ilMailTemplateServiceInterface::class] = static function (Container $c): ilMailTemplateServiceInterface { - return new ilMailTemplateService(new ilMailTemplateRepository($c->database())); + return new ilMailTemplateService( + new ilMailTemplateRepository($c->database()), + $c->mail()->mustacheFactory() + ); }; } } diff --git a/Services/Mail/classes/Templates/TemplateMessageSyntaxException.php b/Services/Mail/classes/Templates/TemplateMessageSyntaxException.php new file mode 100644 index 000000000000..fe3e506d8bd3 --- /dev/null +++ b/Services/Mail/classes/Templates/TemplateMessageSyntaxException.php @@ -0,0 +1,27 @@ +tpl->setOnScreenMessage('success', $this->lng->txt('saved_successfully'), true); $this->ctrl->redirect($this, 'showTemplates'); + } catch (\ILIAS\Mail\Templates\TemplateSubjectSyntaxException) { + $form->getItemByPostVar('m_subject')->setAlert($this->lng->txt('mail_template_invalid_tpl_syntax')); + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('form_input_not_valid')); + } catch (\ILIAS\Mail\Templates\TemplateMessageSyntaxException) { + $form->getItemByPostVar('m_message')->setAlert($this->lng->txt('mail_template_invalid_tpl_syntax')); + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('form_input_not_valid')); } catch (Exception) { $form->getItemByPostVar('context')->setAlert( $this->lng->txt('mail_template_no_valid_context') @@ -222,6 +228,10 @@ protected function updateTemplate(): void $this->ctrl->redirect($this, 'showTemplates'); } catch (OutOfBoundsException) { $this->tpl->setOnScreenMessage('failure', $this->lng->txt('mail_template_missing_id')); + } catch (\ILIAS\Mail\Templates\TemplateSubjectSyntaxException) { + $form->getItemByPostVar('m_subject')->setAlert($this->lng->txt('mail_template_invalid_tpl_syntax')); + } catch (\ILIAS\Mail\Templates\TemplateMessageSyntaxException) { + $form->getItemByPostVar('m_message')->setAlert($this->lng->txt('mail_template_invalid_tpl_syntax')); } catch (Exception) { $form->getItemByPostVar('context')->setAlert( $this->lng->txt('mail_template_no_valid_context') diff --git a/Services/Mail/classes/class.ilMailTemplateService.php b/Services/Mail/classes/class.ilMailTemplateService.php index 849b473dc572..154a23e0cb72 100644 --- a/Services/Mail/classes/class.ilMailTemplateService.php +++ b/Services/Mail/classes/class.ilMailTemplateService.php @@ -18,10 +18,15 @@ declare(strict_types=1); +use ILIAS\Mail\Templates\TemplateSubjectSyntaxException; +use ILIAS\Mail\Templates\TemplateMessageSyntaxException; + class ilMailTemplateService implements ilMailTemplateServiceInterface { - public function __construct(protected ilMailTemplateRepository $repository) - { + public function __construct( + protected ilMailTemplateRepository $repository, + protected ilMustacheFactory $mustacheFactory + ) { } public function createNewTemplate( @@ -31,6 +36,18 @@ public function createNewTemplate( string $message, string $language ): ilMailTemplate { + try { + $this->mustacheFactory->getBasicEngine()->render($subject, []); + } catch (Exception) { + throw new TemplateSubjectSyntaxException('Invalid mail template for subject'); + } + + try { + $this->mustacheFactory->getBasicEngine()->render($message, []); + } catch (Exception) { + throw new TemplateMessageSyntaxException('Invalid mail template for message'); + } + $template = new ilMailTemplate(); $template->setContext($contextId); $template->setTitle($title); @@ -51,6 +68,18 @@ public function modifyExistingTemplate( string $message, string $language ): void { + try { + $this->mustacheFactory->getBasicEngine()->render($subject, []); + } catch (Exception) { + throw new TemplateSubjectSyntaxException('Invalid mail template for subject'); + } + + try { + $this->mustacheFactory->getBasicEngine()->render($message, []); + } catch (Exception) { + throw new TemplateMessageSyntaxException('Invalid mail template for message'); + } + $template = $this->repository->findById($templateId); $template->setContext($contextId); diff --git a/Services/Mail/classes/class.ilObjMailGUI.php b/Services/Mail/classes/class.ilObjMailGUI.php index 0efda3d92490..ca2a490a635a 100755 --- a/Services/Mail/classes/class.ilObjMailGUI.php +++ b/Services/Mail/classes/class.ilObjMailGUI.php @@ -32,6 +32,7 @@ class ilObjMailGUI extends ilObjectGUI private const PASSWORD_PLACE_HOLDER = '***********************'; private readonly ilTabsGUI $tabs; + private readonly ilMustacheFactory $mustache_factory; public function __construct($a_data, int $a_id, bool $a_call_by_reference) { @@ -40,6 +41,7 @@ public function __construct($a_data, int $a_id, bool $a_call_by_reference) parent::__construct($a_data, $a_id, $a_call_by_reference, false); $this->tabs = $DIC->tabs(); + $this->mustache_factory = $DIC->mail()->mustacheFactory(); $this->lng->loadLanguageModule('mail'); } @@ -634,6 +636,33 @@ protected function saveExternalSettingsFormObject(): void return; } + // If all forms in ILIAS use the UI/KS forms (here and in Services/Mail), we should move this to a propert constraint/trafo + $is_valid_template_syntax = $this->refinery->custom()->constraint(function ($value): bool { + try { + $this->mustache_factory->getBasicEngine()->render((string) $value, []); + return true; + } catch (Exception) { + return false; + } + }, $this->lng->txt('mail_template_invalid_tpl_syntax')); + + $valid_templates = true; + foreach (['mail_system_usr_from_name', 'mail_system_sys_signature'] as $template) { + try { + $is_valid_template_syntax->check((string) $form->getInput($template)); + } catch (Exception) { + $form->getItemByPostVar($template)->setAlert( + $is_valid_template_syntax->problemWith((string) $form->getInput($template)) + ); + $valid_templates = false; + } + } + if (!$valid_templates) { + $form->setValuesByPost(); + $this->showExternalSettingsFormObject($form); + return; + } + $this->settings->set('mail_smtp_status', (string) ((int) $form->getInput('mail_smtp_status'))); $this->settings->set('mail_smtp_host', (string) $form->getInput('mail_smtp_host')); $this->settings->set('mail_smtp_port', (string) ((int) $form->getInput('mail_smtp_port'))); diff --git a/Services/Mail/interfaces/interface.ilMailTemplateServiceInterface.php b/Services/Mail/interfaces/interface.ilMailTemplateServiceInterface.php index 28e94bed559d..a5887d2b3a68 100644 --- a/Services/Mail/interfaces/interface.ilMailTemplateServiceInterface.php +++ b/Services/Mail/interfaces/interface.ilMailTemplateServiceInterface.php @@ -18,6 +18,9 @@ declare(strict_types=1); +use ILIAS\Mail\Templates\TemplateSubjectSyntaxException; +use ILIAS\Mail\Templates\TemplateMessageSyntaxException; + interface ilMailTemplateServiceInterface { public function createNewTemplate( @@ -28,6 +31,9 @@ public function createNewTemplate( string $language ): ilMailTemplate; + /** + * @throws TemplateSubjectSyntaxException|TemplateMessageSyntaxException + */ public function modifyExistingTemplate( int $templateId, string $contextId, diff --git a/Services/Mail/test/ilMailTemplateServiceTest.php b/Services/Mail/test/ilMailTemplateServiceTest.php index 492868092db3..add6a95772ac 100644 --- a/Services/Mail/test/ilMailTemplateServiceTest.php +++ b/Services/Mail/test/ilMailTemplateServiceTest.php @@ -52,7 +52,8 @@ public function testDefaultTemplateCanBeSetByContext(): void $repo->expects($this->once())->method('findByContextId')->with($template->getContext())->willReturn($all); $repo->expects($this->exactly(count($all)))->method('store'); - $service = new ilMailTemplateService($repo); + $mustache_factory = $this->getMockBuilder(ilMustacheFactory::class)->getMock(); + $service = new ilMailTemplateService($repo, $mustache_factory); $service->setAsContextDefault($template); @@ -74,7 +75,8 @@ public function testDefaultTemplateForContextCanBeUnset(): void $template->setContext('phpunit'); $repo->expects($this->once())->method('store')->with($template); - $service = new ilMailTemplateService($repo); + $mustache_factory = $this->getMockBuilder(ilMustacheFactory::class)->getMock(); + $service = new ilMailTemplateService($repo, $mustache_factory); $service->unsetAsContextDefault($template); diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 9e9070d8dcdf..5c93193a8c07 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -11391,6 +11391,7 @@ mail#:#mail_template_client#:#Textvorlage mail#:#mail_template_client_info#:#Wählen Sie zwischen einer der verfügbaren Textvorlagen. Sie haben die Möglichkeit, die durch die Textvorlage angebotenen Platzhalter zu nutzen. Bei der Wahl einer Textvorlage werden Nachricht und Betreff in diesem Formular (falls in der Vorlage definiert) ersetzt. mail#:#mail_template_context#:#Kontext mail#:#mail_template_default#:# (Standard) +mail#:#mail_template_invalid_tpl_syntax#:#Es wurde eine ungültige Syntax festgestellt. Bitte prüfen Sie Ihre Eingaben, vor allem hinsichtlich der Platzhalter-Syntax. mail#:#mail_template_missing_id#:#Die Aktion kann nicht ausgeführt werden, da die ID der Textvorlage nicht korrekt übertragen wurde. mail#:#mail_template_no_context_available#:#Aktuell wird von keinem ILIAS-Service oder -Modul ein Kontext bereitgestellt. Das Anlegen oder Bearbeiten von Textvorlagen ist daher nicht möglich. mail#:#mail_template_no_valid_context#:#Der gewählte Kontext ist nicht gültig. diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 52812db9689c..43674066a11c 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -11380,6 +11380,7 @@ mail#:#mail_template_client#:#Text Template mail#:#mail_template_client_info#:#Choose one of the available text templates and make use of it's placeholders. If you choose a text template, body and subject of this form (if defined in the text template) will be replaced. mail#:#mail_template_context#:#Context mail#:#mail_template_default#:# (Default) +mail#:#mail_template_invalid_tpl_syntax#:#An invalid syntax has been detected. Please ensure a valid input, especially regarding the placeholder syntax. mail#:#mail_template_missing_id#:#Can't execute the action because the text template id is missing. mail#:#mail_template_no_context_available#:#There is no context provided by any ILIAS service or module. Thus it is not possible to create or edit a text template. mail#:#mail_template_no_valid_context#:#The given context is not valid.