From be228fa48a20b7711bd411e5d4b527636d976ab4 Mon Sep 17 00:00:00 2001 From: Yurii Date: Thu, 15 Jan 2026 19:41:06 +0200 Subject: [PATCH 1/3] url validator util --- .../Password/Recovery/UrlValidator.php | 4 +- .../Password/Recovery/UrlValidatorUtil.php | 59 +++++++++++++++++++ .../Password/UrlValidatorUtilTest.php | 59 +++++++++++++++++++ 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidatorUtil.php create mode 100644 tests/unit/Espo/Tools/UserSecurity/Password/UrlValidatorUtilTest.php diff --git a/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidator.php b/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidator.php index b0a93eb467..6451dd8369 100644 --- a/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidator.php +++ b/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidator.php @@ -48,7 +48,7 @@ class UrlValidator { $siteUrl = rtrim($this->config->get('siteUrl') ?? '', '/'); - if (str_starts_with($url, $siteUrl)) { + if (UrlValidatorUtil::validate($url, $siteUrl)) { return; } @@ -60,7 +60,7 @@ class UrlValidator foreach ($portals as $portal) { $siteUrl = rtrim($portal->getUrl() ?? '', '/'); - if (str_starts_with($url, $siteUrl)) { + if (UrlValidatorUtil::validate($url, $siteUrl)) { return; } } diff --git a/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidatorUtil.php b/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidatorUtil.php new file mode 100644 index 0000000000..3428ca2cbf --- /dev/null +++ b/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidatorUtil.php @@ -0,0 +1,59 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Tools\UserSecurity\Password\Recovery; + +use const FILTER_VALIDATE_URL; +use const PHP_URL_HOST; + +/** + * @internal + */ +class UrlValidatorUtil +{ + public static function validate(string $url, string $siteUrl): bool + { + $host = parse_url($url, PHP_URL_HOST); + $siteHost = parse_url($siteUrl, PHP_URL_HOST); + + if ($host !== $siteHost) { + return false; + } + + if (!filter_var($url, FILTER_VALIDATE_URL)) { + return false; + } + + if (!str_starts_with($url, $siteUrl)) { + return false; + } + + return true; + } +} diff --git a/tests/unit/Espo/Tools/UserSecurity/Password/UrlValidatorUtilTest.php b/tests/unit/Espo/Tools/UserSecurity/Password/UrlValidatorUtilTest.php new file mode 100644 index 0000000000..8ef985c7e3 --- /dev/null +++ b/tests/unit/Espo/Tools/UserSecurity/Password/UrlValidatorUtilTest.php @@ -0,0 +1,59 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace tests\unit\Espo\Tools\UserSecurity\Password; + +use Espo\Tools\UserSecurity\Password\Recovery\UrlValidatorUtil; +use PHPUnit\Framework\TestCase; + +class UrlValidatorUtilTest extends TestCase +{ + public function testValidate(): void + { + $this->assertTrue( + UrlValidatorUtil::validate('https://test.com', 'https://test.com') + ); + + $this->assertTrue( + UrlValidatorUtil::validate('https://test.com/test', 'https://test.com') + ); + + $this->assertTrue( + UrlValidatorUtil::validate('https://test.com/test', 'https://test.com/test') + ); + + $this->assertFalse( + UrlValidatorUtil::validate('https://test.com.test', 'https://test.com') + ); + + $this->assertFalse( + UrlValidatorUtil::validate('https://test.com.test Date: Thu, 15 Jan 2026 19:47:54 +0200 Subject: [PATCH 2/3] fix url rendering --- client/src/views/user/password-change-request.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/client/src/views/user/password-change-request.js b/client/src/views/user/password-change-request.js index 1c444a3fa8..71b6dc0b83 100644 --- a/client/src/views/user/password-change-request.js +++ b/client/src/views/user/password-change-request.js @@ -139,12 +139,17 @@ export default class extends View { const url = data.url || this.baseUrl; - const msg = this.translate('passwordChangedByRequest', 'messages', 'User') + - ' ' + this.translate('Login', 'labels', 'User') + '.'; + const a = document.createElement('a'); + a.href = url; + a.innerText = this.translate('Login', 'labels', 'User'); + + const message = this.translate('passwordChangedByRequest', 'messages', 'User'); + + const html = this.getHelper().escapeString(message) + ' ' + a.outerHTML; this.$el.find('.msg-box') .removeClass('hidden') - .html('' + msg + ''); + .html('' + html + ''); }) .catch(() => { return $submit.removeClass('disabled'); From 917dbdd3fe75f0c729471f6a80081a7fbf8b04ea Mon Sep 17 00:00:00 2001 From: Yurii Date: Thu, 15 Jan 2026 20:35:58 +0200 Subject: [PATCH 3/3] 9.2.6 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00b7b40fca..b3b38315c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "espocrm", - "version": "9.2.5", + "version": "9.2.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "espocrm", - "version": "9.2.5", + "version": "9.2.6", "hasInstallScript": true, "license": "AGPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index cfcfa3dcb9..246ea8ee3b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "espocrm", - "version": "9.2.5", + "version": "9.2.6", "description": "Open-source CRM.", "repository": { "type": "git",