From 2b5420ee7d6ea29c1096e8aad952dda83862ef08 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Tue, 23 Aug 2022 15:36:48 +1200 Subject: [PATCH 01/10] [CVE-2022-37430] Sanitise mixed case javascript --- src/Forms/HTMLEditor/HTMLEditorSanitiser.php | 2 +- tests/php/Forms/HTMLEditor/HTMLEditorSanitiserTest.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Forms/HTMLEditor/HTMLEditorSanitiser.php b/src/Forms/HTMLEditor/HTMLEditorSanitiser.php index 09ff3e8a4..b35fcf19a 100644 --- a/src/Forms/HTMLEditor/HTMLEditorSanitiser.php +++ b/src/Forms/HTMLEditor/HTMLEditorSanitiser.php @@ -347,7 +347,7 @@ class HTMLEditorSanitiser } // Matches "javascript:" with any arbitrary linebreaks inbetween the characters. - $regex = '/^\s*' . implode('\v*', str_split('javascript:')) . '/'; + $regex = '/^\s*' . implode('\v*', str_split('javascript:')) . '/i'; // Strip out javascript execution in href or src attributes. foreach (['src', 'href'] as $dangerAttribute) { if ($el->hasAttribute($dangerAttribute)) { diff --git a/tests/php/Forms/HTMLEditor/HTMLEditorSanitiserTest.php b/tests/php/Forms/HTMLEditor/HTMLEditorSanitiserTest.php index 4b3695274..14f6771a5 100644 --- a/tests/php/Forms/HTMLEditor/HTMLEditorSanitiserTest.php +++ b/tests/php/Forms/HTMLEditor/HTMLEditorSanitiserTest.php @@ -98,6 +98,12 @@ class HTMLEditorSanitiserTest extends FunctionalTest '', 'Javascript in the src attribute of an iframe is completely removed' ], + [ + 'iframe[src]', + '', + '', + 'Mixed case javascript in the src attribute of an iframe is completely removed' + ], ]; $config = HTMLEditorConfig::get('htmleditorsanitisertest'); From d3c28579b797074f0ef99c0d042d77812b9d33a9 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Tue, 6 Sep 2022 15:06:35 +1200 Subject: [PATCH 02/10] [CVE-2022-38462] Don't allow CRLF in header values --- src/Control/HTTPResponse.php | 10 +++++++++- tests/php/Control/HTTPResponseTest.php | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Control/HTTPResponse.php b/src/Control/HTTPResponse.php index 15eb80520..5f7dc5b4b 100644 --- a/src/Control/HTTPResponse.php +++ b/src/Control/HTTPResponse.php @@ -267,7 +267,7 @@ class HTTPResponse public function addHeader($header, $value) { $header = strtolower($header ?? ''); - $this->headers[$header] = $value; + $this->headers[$header] = $this->sanitiseHeader($value); return $this; } @@ -310,6 +310,14 @@ class HTTPResponse return $this; } + /** + * Sanitise header values to avoid possible XSS vectors + */ + private function sanitiseHeader(string $value): string + { + return preg_replace('/\v/', '', $value); + } + /** * @param string $dest * @param int $code diff --git a/tests/php/Control/HTTPResponseTest.php b/tests/php/Control/HTTPResponseTest.php index 18469ec62..88c1aaeeb 100644 --- a/tests/php/Control/HTTPResponseTest.php +++ b/tests/php/Control/HTTPResponseTest.php @@ -45,6 +45,26 @@ class HTTPResponseTest extends SapphireTest $this->assertEmpty($response->getHeader('X-Animal')); } + public function providerSanitiseHeaders() + { + return [ + 'plain text is retained' => ['some arbitrary value1', 'some arbitrary value1'], + 'special chars are retained' => ['`~!@#$%^&*()_+-=,./<>?;\':"[]{}\\|', '`~!@#$%^&*()_+-=,./<>?;\':"[]{}\\|'], + 'line breaks are removed' => ['no line breaks', "n\ro line \nbreaks\r\n"], + ]; + } + + /** + * @dataProvider providerSanitiseHeaders + */ + public function testSanitiseHeaders(string $expected, string $value) + { + $response = new HTTPResponse(); + + $response->addHeader('X-Sanitised', $value); + $this->assertSame($expected, $response->getHeader('X-Sanitised')); + } + public function providerTestValidStatusCodes() { return [ From 168ca00555ce020fa9fb754e27e0a23a58097ed1 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Tue, 25 Oct 2022 11:35:05 +1300 Subject: [PATCH 03/10] [CVE-2022-38724] Restrict embed shortcode attributes --- .../Shortcodes/EmbedShortcodeProvider.php | 34 +++++++++- .../Shortcodes/EmbedShortcodeProviderTest.php | 64 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/src/View/Shortcodes/EmbedShortcodeProvider.php b/src/View/Shortcodes/EmbedShortcodeProvider.php index 42f8e947a..7d6f2bbc7 100644 --- a/src/View/Shortcodes/EmbedShortcodeProvider.php +++ b/src/View/Shortcodes/EmbedShortcodeProvider.php @@ -16,6 +16,7 @@ use SilverStripe\View\HTML; use SilverStripe\View\Parsers\ShortcodeHandler; use SilverStripe\View\Parsers\ShortcodeParser; use SilverStripe\Control\Director; +use SilverStripe\Core\Config\Configurable; use SilverStripe\Dev\Deprecation; use SilverStripe\View\Embed\EmbedContainer; @@ -26,6 +27,23 @@ use SilverStripe\View\Embed\EmbedContainer; */ class EmbedShortcodeProvider implements ShortcodeHandler { + use Configurable; + + /** + * A whitelist of shortcode attributes which are allowed in the resultant markup. + * Note that the tinymce plugin restricts attributes on the client-side separately. + * + * @config + * @deprecated 4.12.0 Removed without equivalent functionality to replace it + */ + private static array $attribute_whitelist = [ + 'url', + 'thumbnail', + 'class', + 'width', + 'height', + 'caption', + ]; /** * Gets the list of shortcodes provided by this handler @@ -207,9 +225,17 @@ class EmbedShortcodeProvider implements ShortcodeHandler } } + $attributes = static::buildAttributeListFromArguments($arguments, ['width', 'height', 'url', 'caption']); + if (array_key_exists('style', $arguments)) { + $attributes->push(ArrayData::create([ + 'Name' => 'style', + 'Value' => Convert::raw2att($arguments['style']), + ])); + } + $data = [ 'Arguments' => $arguments, - 'Attributes' => static::buildAttributeListFromArguments($arguments, ['width', 'height', 'url', 'caption']), + 'Attributes' => $attributes, 'Content' => DBField::create_field('HTMLFragment', $content) ]; @@ -264,6 +290,12 @@ class EmbedShortcodeProvider implements ShortcodeHandler */ private static function buildAttributeListFromArguments(array $arguments, array $exclude = []): ArrayList { + // Clean out any empty arguments and anything not whitelisted + $whitelist = static::config()->get('attribute_whitelist'); + $arguments = array_filter($arguments, function ($value, $key) use ($whitelist) { + return in_array($key, $whitelist) && strlen(trim($value ?? '')); + }, ARRAY_FILTER_USE_BOTH); + $attributes = ArrayList::create(); foreach ($arguments as $key => $value) { if (in_array($key, $exclude ?? [])) { diff --git a/tests/php/View/Shortcodes/EmbedShortcodeProviderTest.php b/tests/php/View/Shortcodes/EmbedShortcodeProviderTest.php index 0fbab6386..a485793a6 100644 --- a/tests/php/View/Shortcodes/EmbedShortcodeProviderTest.php +++ b/tests/php/View/Shortcodes/EmbedShortcodeProviderTest.php @@ -3,6 +3,7 @@ namespace SilverStripe\View\Tests\Shortcodes; use Psr\SimpleCache\CacheInterface; +use SilverStripe\Core\Config\Config; use SilverStripe\View\Parsers\ShortcodeParser; use SilverStripe\View\Shortcodes\EmbedShortcodeProvider; use SilverStripe\Dev\SapphireTest; @@ -186,4 +187,67 @@ class EmbedShortcodeProviderTest extends EmbedUnitTest EmbedShortcodeProvider::flushCachedShortcodes($parser, $content); $this->assertFalse($cache->has($key)); } + + public function testOnlyWhitelistedAttributesAllowed() + { + $url = 'https://www.youtube.com/watch?v=dM15HfUYwF0'; + $html = $this->getShortcodeHtml( + $url, + $url, + << + EOT, + << $url, + 'caption' => 'A nice video', + 'width' => 778, + 'height' => 437, + 'data-some-value' => 'my-data', + 'onmouseover' => 'alert(2)', + 'style' => 'background-color:red;', + ], + ); + $this->assertEqualIgnoringWhitespace( + <<

A nice video

+ EOT, + $html + ); + } + + public function testWhitelistIsConfigurable() + { + // Allow new whitelisted attribute + Config::modify()->merge(EmbedShortcodeProvider::class, 'attribute_whitelist', ['data-some-value']); + + $url = 'https://www.youtube.com/watch?v=dM15HfUYwF0'; + $html = $this->getShortcodeHtml( + $url, + $url, + << + EOT, + << $url, + 'caption' => 'A nice video', + 'width' => 779, + 'height' => 437, + 'data-some-value' => 'my-data', + 'onmouseover' => 'alert(2)', + 'style' => 'background-color:red;', + ], + ); + $this->assertEqualIgnoringWhitespace( + <<

A nice video

+ EOT, + $html + ); + } } From f8befa3dcf854c07a4e94e61ee6f4fea81c156a3 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Thu, 10 Nov 2022 01:56:20 +0000 Subject: [PATCH 04/10] Update translations --- lang/en.yml | 4 ++-- lang/eo.yml | 1 + lang/es.yml | 15 +++++++++++++++ lang/fr.yml | 15 +++++++++++++++ lang/it.yml | 16 ++++++++++++++++ lang/nl.yml | 47 ++++++++++++++++++++++++----------------------- lang/pl.yml | 28 ++++++++++++++++++++++++++++ 7 files changed, 101 insertions(+), 25 deletions(-) diff --git a/lang/en.yml b/lang/en.yml index 6a44c5e96..c4155571b 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -5,7 +5,7 @@ en: EDITINFO: 'Edit this file' REMOVE: Remove SilverStripe\Control\ChangePasswordEmail_ss: - CHANGEPASSWORDFOREMAIL: 'The password for account with email address {email} has been changed. If you didn''t change your password please change your password using the link below' + CHANGEPASSWORDFOREMAIL: 'The password for account with email address {email} has been changed. If you didn\''t change your password please change your password using the link below' CHANGEPASSWORDTEXT1: 'You changed your password for' CHANGEPASSWORDTEXT3: 'Change password' HELLO: Hi @@ -143,12 +143,12 @@ en: SilverStripe\Forms\TimeField: VALIDATEFORMAT: 'Please enter a valid time format ({format})' SilverStripe\ORM\DataObject: + GENERALSEARCH: 'General Search' PLURALNAME: 'Data Objects' PLURALS: one: 'A Data Object' other: '{count} Data Objects' SINGULARNAME: 'Data Object' - GENERALSEARCH: 'General Search' SilverStripe\ORM\FieldType\DBBoolean: ANY: Any NOANSWER: 'No' diff --git a/lang/eo.yml b/lang/eo.yml index f68c5ae5f..0776ee3e0 100644 --- a/lang/eo.yml +++ b/lang/eo.yml @@ -236,6 +236,7 @@ eo: Sort: 'Ordiga Ordo' has_many_Permissions: Permesoj many_many_Members: Membroj + ValidationIdentifierAlreadyExists: 'Jam ekzistas grupo ({group}) kun la sama {identifier}' SilverStripe\Security\LoginAttempt: Email: 'Retpoŝta adreso' EmailHashed: 'Retpoŝta adreso (haketa)' diff --git a/lang/es.yml b/lang/es.yml index 4e2e4b510..5adee4a6f 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -78,6 +78,7 @@ es: PLURALS: one: 'Un Objeto de Dato' other: '{count} Objetos de Datos' + many: '{count} Objetos de Datos' SINGULARNAME: 'Objeto de Dato' SilverStripe\ORM\FieldType\DBBoolean: ANY: Cualquiera @@ -87,24 +88,30 @@ es: DAYS_SHORT_PLURALS: one: '{count} día' other: '{count} días' + many: '{count} días' HOURS_SHORT_PLURALS: one: '{count} hora' other: '{count} horas' + many: '{count} horas' LessThanMinuteAgo: 'Hace menos de un minuto' MINUTES_SHORT_PLURALS: one: '{count} minuto' other: '{count} minutos' + many: '{count} minutos' MONTHS_SHORT_PLURALS: one: '{count} mes' other: '{count} meses' + many: '{count} meses' SECONDS_SHORT_PLURALS: one: '{count} segundo' other: '{count} segundos' + many: '{count} segundos' TIMEDIFFAGO: 'hace {difference}' TIMEDIFFIN: 'en {difference}' YEARS_SHORT_PLURALS: one: '{count} año' other: '{count} años' + many: '{count} años' SilverStripe\ORM\FieldType\DBEnum: ANY: Cualquiera SilverStripe\ORM\Hierarchy\Hierarchy: @@ -135,6 +142,7 @@ es: PLURALS: one: 'Un Grupo' other: '{count} Grupos' + many: '{count} Grupos' Parent: 'Grupo Padre' RolesAddEditLink: 'Gestionar Roles' SINGULARNAME: Grupo @@ -147,6 +155,7 @@ es: PLURALS: one: 'Un intento de inicio de sesión' other: '{count} Intentos de inicios de sesión' + many: '{count} Intentos de inicios de sesión' SINGULARNAME: 'Intento de ingreso' Status: Estado SilverStripe\Security\Member: @@ -176,6 +185,7 @@ es: PLURALS: one: 'Un Miembro' other: '{count} Miembros' + many: '{count} Miembros' SINGULARNAME: Miembro SUBJECTPASSWORDCHANGED: 'Su contraseña ha sido cambiada' SUBJECTPASSWORDRESET: 'Enlace para restaurar su contraseña' @@ -192,6 +202,7 @@ es: PLURALS: one: 'Una Contraseña de Miembro' other: '{count} Contraseñas de Miembros' + many: '{count} Contraseñas de Miembros' SINGULARNAME: 'Contraseña del Miembro' SilverStripe\Security\PasswordValidator: LOWCHARSTRENGTH: 'Necesitas aumentar la complejidad de tu contraseña agregando los siguientes caracteres: {chars}' @@ -206,6 +217,7 @@ es: PLURALS: one: 'Un Permiso' other: '{count} Permisos' + many: '{count} Permisos' SINGULARNAME: Permiso UserPermissionsIntro: 'Asignar grupos a este usuario ajustará los permisos que tienen. Vea la sección de grupos para obtener información sobre permisos en grupos individuales.' SilverStripe\Security\PermissionCheckboxSetField: @@ -219,6 +231,7 @@ es: PLURALS: one: 'Un Rol' other: '{count} Roles' + many: '{count} Roles' SINGULARNAME: Rol Title: Título SilverStripe\Security\PermissionRoleCode: @@ -226,6 +239,7 @@ es: PLURALS: one: 'Un Código de Rol de Permiso' other: '{count} Códigos de Roles de Permiso' + many: '{count} Códigos de Roles de Permiso' PermsError: 'No se puede asignar permisos privilegiados al código "% s" (requiere acceso de administrador)' SINGULARNAME: 'Código del Rol de Permiso' SilverStripe\Security\RememberLoginHash: @@ -233,6 +247,7 @@ es: PLURALS: one: 'Un Hash de inicio de sesión' other: '{count} Hashes de inicio de sesión' + many: '{count} Hashes de inicio de sesión' SINGULARNAME: 'Hash de inicio de sesión' SilverStripe\Security\Security: ALREADYLOGGEDIN: 'No tiene acceso a esta página. Si posee otra cuenta con los privilegios para acceder a esta página, puede iniciar sesión a continuación.' diff --git a/lang/fr.yml b/lang/fr.yml index 8a87ed38f..d66464adc 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -113,6 +113,7 @@ fr: PLURALS: one: 'Un modèle de donnée' other: '{count} modèles de donnée' + many: '{count} modèles de donnée' SINGULARNAME: 'Modèle de donnée' SilverStripe\ORM\FieldType\DBBoolean: ANY: Tout @@ -122,24 +123,30 @@ fr: DAYS_SHORT_PLURALS: one: '{count} jour' other: '{count} jours' + many: '{count} jours' HOURS_SHORT_PLURALS: one: '{count} heure' other: '{count} heures' + many: '{count} heures' LessThanMinuteAgo: 'moins d''une minute' MINUTES_SHORT_PLURALS: one: '{count} min.' other: '{count} min.' + many: '{count} min.' MONTHS_SHORT_PLURALS: one: '{count} mois' other: '{count} mois' + many: '{count} mois' SECONDS_SHORT_PLURALS: one: '{count} sec.' other: '{count} sec.' + many: '{count} sec.' TIMEDIFFAGO: 'Il y a {difference}' TIMEDIFFIN: 'Dans {difference}' YEARS_SHORT_PLURALS: one: '{count} année' other: '{count} années' + many: '{count} années' SilverStripe\ORM\FieldType\DBEnum: ANY: Tout SilverStripe\ORM\Hierarchy: @@ -182,6 +189,7 @@ fr: PLURALS: one: 'Un groupe' other: '{count} groupes' + many: '{count} groupes' Parent: 'Groupe parent' ROLES: Rôles ROLESDESCRIPTION: 'Les rôles sont des ensembles de permissions prédéfinies, et ils peuvent être attribués à des groupes.
Si nécessaire, ils peuvent hériter de groupes parents.' @@ -198,6 +206,7 @@ fr: PLURALS: one: 'Une tentative de connexion' other: '{count} tentatives de connexion' + many: '{count} tentatives de connexion' SINGULARNAME: 'Tentative de connexion' Status: Statut SilverStripe\Security\Member: @@ -228,6 +237,7 @@ fr: PLURALS: one: 'Un membre' other: '{count} membres' + many: '{count} membres' SINGULARNAME: Membre SUBJECTPASSWORDCHANGED: 'Votre mot de passe a été changé' SUBJECTPASSWORDRESET: 'Lien pour modifier votre mot de passe' @@ -255,6 +265,7 @@ fr: PLURALS: one: 'Un mot de passe utilisateur' other: '{count} mots de passe utilisateur' + many: '{count} mots de passe utilisateur' SINGULARNAME: 'Mot de passe utilisateur' SilverStripe\Security\PasswordValidator: LOWCHARSTRENGTH: 'Veuillez renforcer votre mot de passe en ajoutant certains des caractères suivants : {chars}' @@ -271,6 +282,7 @@ fr: PLURALS: one: 'Une autorisation' other: '{count} autorisations' + many: '{count} autorisations' SINGULARNAME: Permission UserPermissionsIntro: "Assigner des groupes à cet utilisateur modifiera les autorisations dont il dispose. Consultez la section «\_Groupes\_» pour plus de détails sur les autorisations associées à chaque groupe." SilverStripe\Security\PermissionCheckboxSetField: @@ -284,6 +296,7 @@ fr: PLURALS: one: 'Un rôle' other: '{count} rôles' + many: '{count} rôles' SINGULARNAME: Rôle Title: Titre SilverStripe\Security\PermissionRoleCode: @@ -291,6 +304,7 @@ fr: PLURALS: one: 'Un code d''autorisation lié au rôle' other: '{count} codes d''autorisation liés au rôle' + many: '{count} codes d''autorisation liés au rôle' PermsError: 'Impossible d''attribuer le code "{code}" (requiert un accès en tant qu''administrateur)' SINGULARNAME: 'Code d''autorisation lié au rôle' SilverStripe\Security\RememberLoginHash: @@ -298,6 +312,7 @@ fr: PLURALS: one: 'Une signature de mot de passe' other: '{count} signatures de mot de passe' + many: '{count} signatures de mot de passe' SINGULARNAME: 'Signature de mot de passe' SilverStripe\Security\Security: ALREADYLOGGEDIN: 'Vous n''avez pas accès à cette page. Si un autre de vos identifiants vous permet d''accéder à cette page, merci de vous reconnecter ci-dessous en l''utilisant.' diff --git a/lang/it.yml b/lang/it.yml index 111d04835..75d5ee68b 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -149,6 +149,7 @@ it: PLURALS: one: 'Un Data Object' other: '{count} Data Object' + many: '{count} Data Object' SINGULARNAME: 'Data Object' SilverStripe\ORM\FieldType\DBBoolean: ANY: Qualsiasi @@ -158,24 +159,30 @@ it: DAYS_SHORT_PLURALS: one: '{count} giorno' other: '{count} giorni' + many: '{count} giorni' HOURS_SHORT_PLURALS: one: '{count} ora' other: '{count} ore' + many: '{count} ore' LessThanMinuteAgo: 'meno di un minuto' MINUTES_SHORT_PLURALS: one: '{count} minuto' other: '{count} minuti' + many: '{count} minuti' MONTHS_SHORT_PLURALS: one: '{count} mese' other: '{count} mesi' + many: '{count} mesi' SECONDS_SHORT_PLURALS: one: '{count} secondo' other: '{count} secondi' + many: '{count} secondi' TIMEDIFFAGO: '{difference} fa' TIMEDIFFIN: 'in {difference}' YEARS_SHORT_PLURALS: one: '{count} anno' other: '{count} anni' + many: '{count} anni' SilverStripe\ORM\FieldType\DBEnum: ANY: Qualsiasi SilverStripe\ORM\FieldType\DBForeignKey: @@ -228,6 +235,7 @@ it: PLURALS: one: 'Un Gruppo' other: '{count} Gruppi' + many: '{count} Gruppi' Parent: 'Gruppo padre' ROLES: Ruoli ROLESDESCRIPTION: 'I ruoli sono insiemi predefiniti di permessi che possono essere assegnati ai gruppi.
Se richiesto possono essere ereditati dai gruppi padre.' @@ -236,6 +244,7 @@ it: Sort: 'Tipo ordinamento' has_many_Permissions: Permessi many_many_Members: Membri + ValidationIdentifierAlreadyExists: 'Esiste già un gruppo ({group}) con lo stesso {identifier}' SilverStripe\Security\LoginAttempt: Email: 'Indirizzo e-mail' EmailHashed: 'Indirizzo email (hash)' @@ -244,6 +253,7 @@ it: PLURALS: one: 'Un tentativo d''accesso' other: '{count} tentativi d''accesso' + many: '{count} tentativi d''accesso' SINGULARNAME: 'Tentativo d''accesso' Status: Stato SilverStripe\Security\Member: @@ -276,6 +286,7 @@ it: PLURALS: one: 'Un Utente' other: '{count} Utenti' + many: '{count} Utenti' SINGULARNAME: Utente SUBJECTPASSWORDCHANGED: 'La tua password è stata cambiata' SUBJECTPASSWORDRESET: 'Link per azzerare la tua password' @@ -305,6 +316,7 @@ it: PLURALS: one: 'Una password utente' other: '{count} password utente' + many: '{count} password utente' SINGULARNAME: 'Password utente' SilverStripe\Security\PasswordValidator: LOWCHARSTRENGTH: 'Perfavore aumenta la sicurezza della password aggiungendo alcuni dei seguenti caratteri: {chars}' @@ -321,6 +333,7 @@ it: PLURALS: one: 'Un Permesso' other: '{count} Permessi' + many: '{count} Permessi' SINGULARNAME: Permesso UserPermissionsIntro: 'Assegnando gruppi a questo utente modificherà i suoi permessi. Vedi la sezione gruppi per dettagli sui permessi dei singoli gruppi.' SilverStripe\Security\PermissionCheckboxSetField: @@ -334,6 +347,7 @@ it: PLURALS: one: 'Un Ruolo' other: '{count} Ruoli' + many: '{count} Ruoli' SINGULARNAME: Ruolo Title: Titolo SilverStripe\Security\PermissionRoleCode: @@ -341,6 +355,7 @@ it: PLURALS: one: 'Un codice di ruolo' other: '{count} codici di ruolo' + many: '{count} codici di ruolo' PermsError: 'Non posso assegnare permessi privilegiati al codice "{code}" (richiede accesso ADMIN)' SINGULARNAME: 'Codice di ruolo' SilverStripe\Security\RememberLoginHash: @@ -348,6 +363,7 @@ it: PLURALS: one: 'Un Hash di Login' other: '{count} Hash di Login' + many: '{count} Hash di Login' SINGULARNAME: 'Hash di Login' SilverStripe\Security\Security: ALREADYLOGGEDIN: 'Non hai accesso a questa pagina. Se hai un altro account che può accederci, puoi autenticarti qui sotto.' diff --git a/lang/nl.yml b/lang/nl.yml index 31d6452c1..9d7104cb5 100644 --- a/lang/nl.yml +++ b/lang/nl.yml @@ -7,15 +7,15 @@ nl: EDITINFO: 'Bewerk dit bestand' REMOVE: Verwijder SilverStripe\Control\ChangePasswordEmail_ss: - CHANGEPASSWORDFOREMAIL: 'Het wachtwoord voor het account met e-mailadres {email} is aangepast. Indien u uw wachtwoord niet heeft aangepast kunt u dat doen met onderstaande link.' + CHANGEPASSWORDFOREMAIL: 'Het wachtwoord voor het account met e-mailadres {email} is zojuist aangepast. Heb je geen wachtwoord aangepast, gebruik van onderstaande knop om uit veiligheidsoverwegingen je wachtwoord opnieuw in te stellen.' CHANGEPASSWORDTEXT1: 'U heeft het wachtwoord veranderd voor' - CHANGEPASSWORDTEXT3: 'Wachtwoord veranderen' + CHANGEPASSWORDTEXT3: 'Wachtwoord opnieuw instellen' HELLO: Hallo SilverStripe\Control\Email\ForgotPasswordEmail_ss: HELLO: Hallo - TEXT1: 'Hier is uw' - TEXT2: 'link om uw wachtwoord opnieuw aan te maken' - TEXT3: voor + TEXT1: 'Je hebt aangegeven je wachtwoord te zijn vergeten. Klik op de' + TEXT2: 'onderstaande link' + TEXT3: 'om je wachtwoord opnieuw in te stellen voor ' SilverStripe\Control\Middleware\ConfirmationMiddleware\GetParameter: CONFIRMATION_NAME: '"{key}" GET parameter' SilverStripe\Control\Middleware\ConfirmationMiddleware\Url: @@ -39,8 +39,8 @@ nl: ATLEAST: 'Een wachtwoord moet tenminste {min} karakters hebben.' BETWEEN: 'Een wachtwoord moet tussen de {min} en {max} karakters hebben' CURRENT_PASSWORD_ERROR: 'Het wachtwoord dat u heeft ingevoerd is niet juist.' - CURRENT_PASSWORD_MISSING: 'Voer uw huidige wachtwoord in.' - LOGGED_IN_ERROR: 'U moet ingelogd zijn om uw wachtwoord te kunnen veranderen!' + CURRENT_PASSWORD_MISSING: 'Voer je huidige wachtwoord in.' + LOGGED_IN_ERROR: 'Je moet ingelogd zijn om je wachtwoord te kunnen veranderen!' MAXIMUM: 'Een wachtwoord mag maximaal {max} karakters hebben.' SHOWONCLICKTITLE: 'Verander wachtwoord' SilverStripe\Forms\DateField: @@ -63,7 +63,7 @@ nl: FIELDNOTSET: 'Bestandsinformatie niet gevonden' SilverStripe\Forms\Form: BAD_METHOD: 'Dit formulier moet middels {method} verzonden worden' - CSRF_EXPIRED_MESSAGE: 'Uw sessie is verlopen. Verzend het formulier opnieuw.' + CSRF_EXPIRED_MESSAGE: 'De sessie is verlopen. Verzend het formulier opnieuw.' CSRF_FAILED_MESSAGE: 'Er lijkt een technisch probleem te zijn. Klik op de knop terug, vernieuw uw browser, en probeer het opnieuw.' FIELDISREQUIRED: '{name} is verplicht' VALIDATIONPASSWORDSDONTMATCH: 'Wachtwoorden komen niet overeen' @@ -194,7 +194,7 @@ nl: ERRORNOTADMIN: 'Die gebruiker is geen beheerder.' ERRORNOTREC: 'De gebruikersnaam en/of het wachtwoord wordt niet herkend' SilverStripe\Security\CMSMemberLoginForm: - PASSWORDEXPIRED: '

Uw wachtwoord is verlopen. Kies een nieuw wachtwoord.

' + PASSWORDEXPIRED: 'Je wachtwoord is verlopen. Kies een nieuw wachtwoord.

' SilverStripe\Security\CMSSecurity: INVALIDUSER: '

Ongeldige gebruiker Log hier opnieuw in om verder te gaan.

' LOGIN_MESSAGE: '

De browsersessie is verlopen wegens inactiviteit

' @@ -236,6 +236,7 @@ nl: Sort: Sorteer-richting has_many_Permissions: Rechten many_many_Members: Leden + ValidationIdentifierAlreadyExists: 'Er bestaat al een groep ({group}) met eenzelfde {identifier}' SilverStripe\Security\LoginAttempt: Email: 'E-mailadres ' EmailHashed: 'E-mailadres (versleuteld)' @@ -259,7 +260,7 @@ nl: EDIT_PASSWORD: 'Nieuw wachtwoord' EMAIL: E-mail EMPTYNEWPASSWORD: 'Het nieuwe wachtwoord mag niet leeg zijn, probeer opnieuw' - ENTEREMAIL: 'Typ uw e-mailadres om een link te ontvangen waarmee u uw wachtwoord kunt resetten.' + ENTEREMAIL: 'Typ je e-mailadres om een link te ontvangen, waarmee je je wachtwoord opnieuw kan instellen.' ERRORLOCKEDOUT2: 'Uw account is tijdelijk uitgeschakeld als gevolg van te veel mislukte pogingen om in te loggen. Probeer het over {count} minuten opnieuw.' ERRORNEWPASSWORD: 'De nieuwe wachtwoorden komen niet overeen, probeer het nogmaals' ERRORPASSWORDNOTMATCH: 'Uw huidige wachtwoord is niet correct, probeer het nogmaals' @@ -269,20 +270,20 @@ nl: LOGGEDINAS: 'U bent ingelogd als {name}.' NEWPASSWORD: 'Nieuw wachtwoord' PASSWORD: Wachtwoord - PASSWORDEXPIRED: 'Uw wachtwoord is verlopen. Kies een nieuw wachtwoord.' + PASSWORDEXPIRED: 'Je wachtwoord is verlopen. Kies een nieuw wachtwoord.' PLURALNAME: Leden PLURALS: one: 'Een lid' other: '{count} leden' SINGULARNAME: Lid - SUBJECTPASSWORDCHANGED: 'Uw wachtwoord is veranderd' - SUBJECTPASSWORDRESET: 'Link om uw wachtwoord opnieuw aan te maken' + SUBJECTPASSWORDCHANGED: 'Je wachtwoord is aangepast' + SUBJECTPASSWORDRESET: 'Je wachtwoord opnieuw instellen' SURNAME: Achternaam - VALIDATIONADMINLOSTACCESS: 'Niet mogelijk om alle admin-groepen te verwijderen van uw profiel' - VALIDATIONMEMBEREXISTS: 'Er bestaat al een gebruiker met eenzelfde %s' + VALIDATIONADMINLOSTACCESS: 'Niet mogelijk om alle admin-groepen te verwijderen van je profiel' + VALIDATIONMEMBEREXISTS: 'Er bestaat al een gebruiker met dezelfde {identifier}' ValidationIdentifierFailed: 'Een bestaande gebruiker #{id} kan niet dezelfde unieke velden hebben ({name} = {value}))' WELCOMEBACK: 'Welkom terug, {firstname}' - YOUROLDPASSWORD: 'Uw oude wachtwoord' + YOUROLDPASSWORD: 'Je oude wachtwoord' belongs_many_many_Groups: Groepen db_Locale: 'Interface taal' db_LockedOutUntil: 'Gesloten tot' @@ -307,8 +308,8 @@ nl: other: '{count} Gebruikerswachtwoorden' SINGULARNAME: Gebruikerswachtwoord SilverStripe\Security\PasswordValidator: - LOWCHARSTRENGTH: 'Maak a.u.b. uw wachtwoord sterker door enkele van de volgende karakters te gebruiken: {chars}' - PREVPASSWORD: 'U heeft dit wachtwoord in het verleden al gebruikt, kies a.u.b. een nieuw wachtwoord.' + LOWCHARSTRENGTH: 'Maak a.u.b. het wachtwoord sterker door enkele van de volgende karakters te gebruiken: {chars}' + PREVPASSWORD: 'Dit wachtwoord is in het verleden al gebruikt, kies a.u.b. een nieuw wachtwoord.' TOOSHORT: 'Het wachtwoord is te kort, het moet minimaal {minimum} karakters hebben' SilverStripe\Security\Permission: AdminGroup: Beheerder @@ -352,18 +353,18 @@ nl: SilverStripe\Security\Security: ALREADYLOGGEDIN: 'U hebt geen toegang tot deze pagina. Als u een andere account met de nodige rechten hebt, kan u hieronder opnieuw inloggen.' BUTTONSEND: 'Nieuw wachtwoord aanmaken' - CHANGEPASSWORDBELOW: 'U kunt uw wachtwoord hieronder veranderen.' - CHANGEPASSWORDHEADER: 'Verander uw wachtwoord' + CHANGEPASSWORDBELOW: 'Je kan hieronder het wachtwoord veranderen.' + CHANGEPASSWORDHEADER: 'Wachtwoord veranderen' CONFIRMLOGOUT: 'Klik op onderstaande knop om uit te loggen.' ENTERNEWPASSWORD: 'Voer een nieuw wachtwoord in.' - ERRORPASSWORDPERMISSION: 'U moet ingelogd zijn om uw wachtwoord te kunnen veranderen!' + ERRORPASSWORDPERMISSION: 'Je moet ingelogd zijn om je wachtwoord te kunnen veranderen!' LOGIN: Inloggen LOGOUT: Uitloggen LOSTPASSWORDHEADER: 'Wachtwoord vergeten' NOTEPAGESECURED: 'Deze pagina is beveiligd. Voer uw gegevens in en u wordt automatisch doorgestuurd.' NOTERESETLINKINVALID: '

De reset link is ongeldig of verlopen.

Je kan hier een nieuwe link aanvragen of het wachtwoord veranderen nadat je bent ingelogd.

' - NOTERESETPASSWORD: 'Voer uw e-mailadres in en we sturen een link waarmee u een nieuw wachtwoord kunt instellen.' + NOTERESETPASSWORD: 'Voer je e-mailadres in en we sturen een link waarmee je een nieuw wachtwoord kunt instellen.' PASSWORDRESETSENTHEADER: Verzonden - PASSWORDRESETSENTTEXT: 'Bedankt! Er is een link verstuurd om uw wachtwoord opnieuw in te stellen (mits het e-mailadres reeds bekend is bij ons).' + PASSWORDRESETSENTTEXT: 'Bedankt! Er is een e-mail verstuurd om je wachtwoord opnieuw in te stellen - mits het e-mailadres reeds bekend is bij ons.' SilverStripe\View\Shortcodes\EmbedShortcodeProvider: INVALID_URL: 'Het inladen van de media-bestanden is mislukt.' diff --git a/lang/pl.yml b/lang/pl.yml index 219f2274d..4e0bcec5d 100644 --- a/lang/pl.yml +++ b/lang/pl.yml @@ -62,6 +62,7 @@ pl: VALIDATIONSTRONGPASSWORD: 'Hasła muszą mieć przynajmniej jedną cyfrę oraz jeden znak alfanumeryczny.' VALIDATOR: Walidator VALIDCURRENCY: 'Proszę podaj prawidłową walutę' + FIELDISREQUIRED: 'Pole {name} jest wymagane' SilverStripe\Forms\FormField: EXAMPLE: 'na przykład {format}' NONE: brak @@ -97,10 +98,13 @@ pl: DeletePermissionsFailure: 'Brak uprawnień do usuwania' Deleted: 'Usunięto {type} {name}' Save: Zapisz + Saved: 'Zapisane {name} {link}' SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest: NEW: Dodaj NEXT: 'Przejdź do następnego rekordu' PREVIOUS: 'Przejdź do poprzedniego rekordu' + EditPermissionsFailure: 'Wygląda na to, że nie masz wystarczających uprawnień, aby zmienić {ObjectTitle}' + ViewPermissionsFailure: 'Wygląda na to, że nie masz wystarczających uprawnień, aby zobaczyć {ObjectTitle}' SilverStripe\Forms\GridField\GridFieldEditButton: EDIT: Zmień SilverStripe\Forms\GridField\GridFieldFilterHeader: @@ -232,6 +236,7 @@ pl: Sort: 'Kolejność Sortowania' has_many_Permissions: Zezwolenia many_many_Members: Użytkownicy + ValidationIdentifierAlreadyExists: 'Grupa ({group}) już istnieje z tym samym {identifier}' SilverStripe\Security\LoginAttempt: Email: 'Adres e-mail' EmailHashed: 'Adres e-mail (hashed)' @@ -286,6 +291,10 @@ pl: db_LockedOutUntil: 'Zablokowany do' db_Password: Hasło db_PasswordExpiry: 'Data wygaśnięcia hasła' + KEEP_ME_SIGNED_IN: 'Zapamiętaj mnie / Nie wylogowuj mnie przez {count} dni' + KEEP_ME_SIGNED_IN_TOOLTIP: 'Pozostaniesz uwierzytelniony na tym urządzeniu przez {count} dni. Używaj tej funkcji tylko wtedy, gdy ufasz urządzeniu, z którego korzystasz.' + VALIDATIONMEMBEREXISTS: 'Użytkownik z tym adresem już istnieje {identifier}' + WELCOMEBACK: 'Witaj ponownie, {firstname}' SilverStripe\Security\MemberAuthenticator\CMSMemberLoginForm: AUTHENTICATORNAME: 'Formularz logowania użytkownika CMS' BUTTONFORGOTPASSWORD: 'Zapomniałeś hasła?' @@ -370,3 +379,22 @@ pl: NOTERESETPASSWORD: 'Wpisz adres e-mail, na który mamy wysłać link gdzie możesz zresetować swoje hasło' PASSWORDRESETSENTHEADER: 'Link resetowania hasła wysłany' PASSWORDRESETSENTTEXT: 'Dziękujemy! Link resetujący hasło został wysłany do ''{email}'', o ile konto użytkownika dla takiego e-maila istnieje.' + NOTERESETLINKINVALID: '

Link resetujący hasło wygasł lub jest nieprawidłowy.

Możesz poprosić o nowy tutaj lub zmień swoje hasło po zalogowaniu się.

' + SilverStripe\Control\Middleware\ConfirmationMiddleware\GetParameter: + CONFIRMATION_NAME: 'Parametr GET „{key}”' + SilverStripe\Control\Middleware\ConfirmationMiddleware\Url: + CONFIRMATION_DESCRIPTION: 'Adres URL to: „{url}”' + CONFIRMATION_NAME: 'Adres URL jest chroniony' + SilverStripe\Control\Middleware\ConfirmationMiddleware\UrlPathStartswith: + CONFIRMATION_DESCRIPTION: 'Pełny adres URL to: „{url}”' + CONFIRMATION_NAME: 'URL zaczyna się od „{path}”' + SilverStripe\Forms\HTMLEditor\TinyMCEConfig: + BEST_FIT: 'Dopasuj optymalnie' + ORIGINAL: Oryginał + PIXEL_WIDTH: '{width} pikseli' + SilverStripe\Forms\TextField: + VALIDATEMAXLENGTH: 'Wartość {name} nie może przekraczać {maxLength} znaków długości' + SilverStripe\ORM\FieldType\DBString: + ELLIPSIS: ... + SilverStripe\View\Shortcodes\EmbedShortcodeProvider: + INVALID_URL: 'Podczas ładowania pliku wystąpił problem.' From 4308a93cc81a75227bcbfc1abd4aaf5e21ef21ee Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Mon, 21 Nov 2022 13:01:32 +1300 Subject: [PATCH 05/10] [CVE-2022-38148] Validate SortColumn exists --- .../GridField/GridFieldSortableHeader.php | 11 ++++++ .../GridField/GridFieldSortableHeaderTest.php | 36 +++++++++++++++---- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/Forms/GridField/GridFieldSortableHeader.php b/src/Forms/GridField/GridFieldSortableHeader.php index c9e9e03e5..61cf81ca9 100644 --- a/src/Forms/GridField/GridFieldSortableHeader.php +++ b/src/Forms/GridField/GridFieldSortableHeader.php @@ -11,6 +11,7 @@ use SilverStripe\ORM\DataObject; use SilverStripe\View\ArrayData; use SilverStripe\View\SSViewer; use LogicException; +use SilverStripe\Core\Injector\Injector; /** * GridFieldSortableHeader adds column headers to a {@link GridField} that can @@ -271,6 +272,16 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM return $dataList; } + // Prevent SQL Injection by validating that SortColumn exists + /** @var GridFieldDataColumns $columns */ + $columns = $gridField->getConfig()->getComponentByType(GridFieldDataColumns::class); + $fields = $columns->getDisplayFields($gridField); + if (!array_key_exists($state->SortColumn, $fields) && + !in_array($state->SortColumn, $this->getFieldSorting()) + ) { + throw new LogicException('Invalid SortColumn: ' . $state->SortColumn); + } + return $dataList->sort($state->SortColumn, $state->SortDirection('asc')); } diff --git a/tests/php/Forms/GridField/GridFieldSortableHeaderTest.php b/tests/php/Forms/GridField/GridFieldSortableHeaderTest.php index 5973f1589..5a48de8ec 100644 --- a/tests/php/Forms/GridField/GridFieldSortableHeaderTest.php +++ b/tests/php/Forms/GridField/GridFieldSortableHeaderTest.php @@ -71,17 +71,18 @@ class GridFieldSortableHeaderTest extends SapphireTest $list = Team::get()->filter([ 'ClassName' => Team::class ]); $config = new GridFieldConfig_RecordEditor(); $gridField = new GridField('testfield', 'testfield', $list, $config); + $component = $gridField->getConfig()->getComponentByType(GridFieldSortableHeader::class); // Test normal sorting + $component->setFieldSorting(['Name' => 'City']); $state = $gridField->State->GridFieldSortableHeader; $state->SortColumn = 'City'; $state->SortDirection = 'asc'; - $compontent = $gridField->getConfig()->getComponentByType(GridFieldSortableHeader::class); - $listA = $compontent->getManipulatedData($gridField, $list); + $listA = $component->getManipulatedData($gridField, $list); $state->SortDirection = 'desc'; - $listB = $compontent->getManipulatedData($gridField, $list); + $listB = $component->getManipulatedData($gridField, $list); $this->assertEquals( ['Auckland', 'Cologne', 'Melbourne', 'Wellington'], @@ -93,12 +94,13 @@ class GridFieldSortableHeaderTest extends SapphireTest ); // Test one relation 'deep' + $component->setFieldSorting(['Name' => 'Cheerleader.Name']); $state->SortColumn = 'Cheerleader.Name'; $state->SortDirection = 'asc'; - $relationListA = $compontent->getManipulatedData($gridField, $list); + $relationListA = $component->getManipulatedData($gridField, $list); $state->SortDirection = 'desc'; - $relationListB = $compontent->getManipulatedData($gridField, $list); + $relationListB = $component->getManipulatedData($gridField, $list); $this->assertEquals( ['Wellington', 'Melbourne', 'Cologne', 'Auckland'], @@ -110,12 +112,13 @@ class GridFieldSortableHeaderTest extends SapphireTest ); // Test two relations 'deep' + $component->setFieldSorting(['Name' => 'Cheerleader.Hat.Colour']); $state->SortColumn = 'Cheerleader.Hat.Colour'; $state->SortDirection = 'asc'; - $relationListC = $compontent->getManipulatedData($gridField, $list); + $relationListC = $component->getManipulatedData($gridField, $list); $state->SortDirection = 'desc'; - $relationListD = $compontent->getManipulatedData($gridField, $list); + $relationListD = $component->getManipulatedData($gridField, $list); $this->assertEquals( ['Cologne', 'Auckland', 'Wellington', 'Melbourne'], @@ -139,6 +142,7 @@ class GridFieldSortableHeaderTest extends SapphireTest $component = $gridField->getConfig()->getComponentByType(GridFieldSortableHeader::class); // Test that inherited dataobjects will work correctly + $component->setFieldSorting(['Name' => 'Cheerleader.Hat.Colour']); $state->SortColumn = 'Cheerleader.Hat.Colour'; $state->SortDirection = 'asc'; $relationListA = $component->getManipulatedData($gridField, $list); @@ -179,6 +183,7 @@ class GridFieldSortableHeaderTest extends SapphireTest ); // Test subclasses of tables + $component->setFieldSorting(['Name' => 'CheerleadersMom.Hat.Colour']); $state->SortColumn = 'CheerleadersMom.Hat.Colour'; $state->SortDirection = 'asc'; $relationListB = $component->getManipulatedData($gridField, $list); @@ -229,4 +234,21 @@ class GridFieldSortableHeaderTest extends SapphireTest $relationListBdesc->column('City') ); } + + public function testSortColumnValidation() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Invalid SortColumn: INVALID'); + + $list = Team::get()->filter([ 'ClassName' => Team::class ]); + $config = new GridFieldConfig_RecordEditor(); + $gridField = new GridField('testfield', 'testfield', $list, $config); + $component = $gridField->getConfig()->getComponentByType(GridFieldSortableHeader::class); + + $state = $gridField->State->GridFieldSortableHeader; + $state->SortColumn = 'INVALID'; + $state->SortDirection = 'asc'; + + $component->getManipulatedData($gridField, $list); + } } From fe13856769492a9bf584c824843153864cf8c725 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Mon, 21 Nov 2022 13:06:40 +1300 Subject: [PATCH 06/10] [CVE-2022-37429] Sanitise XSS --- src/Forms/HTMLEditor/HTMLEditorSanitiser.php | 4 ++-- .../HTMLEditor/HTMLEditorSanitiserTest.php | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/Forms/HTMLEditor/HTMLEditorSanitiser.php b/src/Forms/HTMLEditor/HTMLEditorSanitiser.php index b35fcf19a..a075d98fa 100644 --- a/src/Forms/HTMLEditor/HTMLEditorSanitiser.php +++ b/src/Forms/HTMLEditor/HTMLEditorSanitiser.php @@ -347,9 +347,9 @@ class HTMLEditorSanitiser } // Matches "javascript:" with any arbitrary linebreaks inbetween the characters. - $regex = '/^\s*' . implode('\v*', str_split('javascript:')) . '/i'; + $regex = '/^\s*' . implode('\s*', str_split('javascript:')) . '/i'; // Strip out javascript execution in href or src attributes. - foreach (['src', 'href'] as $dangerAttribute) { + foreach (['src', 'href', 'data'] as $dangerAttribute) { if ($el->hasAttribute($dangerAttribute)) { if (preg_match($regex, $el->getAttribute($dangerAttribute))) { $el->removeAttribute($dangerAttribute); diff --git a/tests/php/Forms/HTMLEditor/HTMLEditorSanitiserTest.php b/tests/php/Forms/HTMLEditor/HTMLEditorSanitiserTest.php index 14f6771a5..3d5c3d5c6 100644 --- a/tests/php/Forms/HTMLEditor/HTMLEditorSanitiserTest.php +++ b/tests/php/Forms/HTMLEditor/HTMLEditorSanitiserTest.php @@ -104,6 +104,30 @@ class HTMLEditorSanitiserTest extends FunctionalTest '', 'Mixed case javascript in the src attribute of an iframe is completely removed' ], + [ + 'iframe[src]', + "", + '', + 'Javascript with tab elements the src attribute of an iframe is completely removed' + ], + [ + 'object[data]', + '', + '', + 'Object with OK content in the data attribute is retained' + ], + [ + 'object[data]', + '', + '', + 'Object with dangerous content in data attribute is completely removed' + ], + [ + 'img[src]', + '', + '', + 'XSS vulnerable attributes starting with on or style are removed via configuration' + ], ]; $config = HTMLEditorConfig::get('htmleditorsanitisertest'); From 31d5aef520f8c8b0c971d87ba62963dd0e1fb9f4 Mon Sep 17 00:00:00 2001 From: Chris Penny Date: Thu, 24 Nov 2022 13:18:56 +1300 Subject: [PATCH 07/10] Bugfix: SSViewer check object exists before calling prop or method --- src/View/SSViewer_DataPresenter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/View/SSViewer_DataPresenter.php b/src/View/SSViewer_DataPresenter.php index 26cdf70c3..9319f04d3 100644 --- a/src/View/SSViewer_DataPresenter.php +++ b/src/View/SSViewer_DataPresenter.php @@ -355,7 +355,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope // Check if the method to-be-called exists on the target object - if so, don't check any further // injection locations $on = $this->itemIterator ? $this->itemIterator->current() : $this->item; - if (isset($on->$property) || method_exists($on, $property ?? '')) { + if ($on && (isset($on->$property) || method_exists($on, $property ?? ''))) { return null; } From b107622400d47eb5700cb8c332c2c68c4896dcb4 Mon Sep 17 00:00:00 2001 From: Michal Kleiner Date: Tue, 29 Nov 2022 15:07:56 +1300 Subject: [PATCH 08/10] FIX Improve rounding logic for storing of long decimal numbers (#10593) Co-authored-by: Michal Kleiner --- src/ORM/FieldType/DBDecimal.php | 2 +- tests/php/ORM/DecimalTest.php | 29 ++++++++++++++++++++++++ tests/php/ORM/DecimalTest/TestObject.php | 7 ++++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/ORM/FieldType/DBDecimal.php b/src/ORM/FieldType/DBDecimal.php index 5af228bf8..eb2941afb 100644 --- a/src/ORM/FieldType/DBDecimal.php +++ b/src/ORM/FieldType/DBDecimal.php @@ -126,7 +126,7 @@ class DBDecimal extends DBField return 0; } - if (ctype_digit((string) $value)) { + if (abs((float) $value - (int) $value) < PHP_FLOAT_EPSILON) { return (int)$value; } diff --git a/tests/php/ORM/DecimalTest.php b/tests/php/ORM/DecimalTest.php index c61f81d9f..fffd2aecb 100644 --- a/tests/php/ORM/DecimalTest.php +++ b/tests/php/ORM/DecimalTest.php @@ -61,6 +61,35 @@ class DecimalTest extends SapphireTest ); } + public function testLongValueStoredCorrectly() + { + $this->assertEquals( + $this->testDataObject->MyDecimal5, + 1.0, + 'Long default long decimal value is rounded correctly' + ); + + $this->assertEqualsWithDelta( + $this->testDataObject->MyDecimal5, + 0.99999999999999999999, + PHP_FLOAT_EPSILON, + 'Long default long decimal value is correct within float epsilon' + ); + + $this->assertEquals( + $this->testDataObject->MyDecimal6, + 8.0, + 'Long decimal value with a default value is rounded correctly' + ); + + $this->assertEqualsWithDelta( + $this->testDataObject->MyDecimal6, + 7.99999999999999999999, + PHP_FLOAT_EPSILON, + 'Long decimal value is within epsilon if longer than allowed number of float digits' + ); + } + public function testScaffoldFormField() { /** @var DBDecimal $decimal */ diff --git a/tests/php/ORM/DecimalTest/TestObject.php b/tests/php/ORM/DecimalTest/TestObject.php index 1dce40f4b..2dd5da898 100644 --- a/tests/php/ORM/DecimalTest/TestObject.php +++ b/tests/php/ORM/DecimalTest/TestObject.php @@ -14,10 +14,13 @@ class TestObject extends DataObject implements TestOnly 'MyDecimal1' => 'Decimal', 'MyDecimal2' => 'Decimal(5,3,2.5)', 'MyDecimal3' => 'Decimal(4,2,"Invalid default value")', - 'MyDecimal4' => 'Decimal' + 'MyDecimal4' => 'Decimal', + 'MyDecimal5' => 'Decimal(20,18,0.99999999999999999999)', + 'MyDecimal6' => 'Decimal', ]; private static $defaults = [ - 'MyDecimal4' => 4 + 'MyDecimal4' => 4, + 'MyDecimal6' => 7.99999999999999999999, ]; } From 2c105cffc9436a0587381956118fe3361bad04d6 Mon Sep 17 00:00:00 2001 From: Mojmir Fendek Date: Fri, 13 Jan 2023 09:43:22 +1300 Subject: [PATCH 09/10] ENH: saveInto() new extension points. (#10636) * ENH: saveInto() new extension points. --- src/Forms/Form.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Forms/Form.php b/src/Forms/Form.php index 9e0f852db..838781221 100644 --- a/src/Forms/Form.php +++ b/src/Forms/Form.php @@ -1487,16 +1487,21 @@ class Form extends ViewableData implements HasRequestHandler */ public function saveInto(DataObjectInterface $dataObject, $fieldList = null) { + $form = $this; + $dataObject->invokeWithExtensions('onBeforeFormSaveInto', $form, $fieldList); + $dataFields = $this->fields->saveableFields(); $lastField = null; + if ($dataFields) { foreach ($dataFields as $field) { - // Skip fields that have been excluded + // Skip fields that have been excluded if ($fieldList && is_array($fieldList) && !in_array($field->getName(), $fieldList ?? [])) { continue; } $saveMethod = "save{$field->getName()}"; + if ($field->getName() == "ClassName") { $lastField = $field; } elseif ($dataObject->hasMethod($saveMethod)) { @@ -1506,9 +1511,12 @@ class Form extends ViewableData implements HasRequestHandler } } } + if ($lastField) { $lastField->saveInto($dataObject); } + + $dataObject->invokeWithExtensions('onAfterFormSaveInto', $form, $fieldList); } /** From b973c88648012149a4c9983b577312c7b7e8a5a7 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Mon, 16 Jan 2023 15:28:23 +1300 Subject: [PATCH 10/10] API Deprecate HTML4Value --- src/View/Parsers/HTML4Value.php | 16 ++++++++++++++++ tests/php/View/Parsers/HTML4ValueTest.php | 9 +++++++++ 2 files changed, 25 insertions(+) diff --git a/src/View/Parsers/HTML4Value.php b/src/View/Parsers/HTML4Value.php index 528802df9..263f59641 100644 --- a/src/View/Parsers/HTML4Value.php +++ b/src/View/Parsers/HTML4Value.php @@ -2,8 +2,24 @@ namespace SilverStripe\View\Parsers; +use SilverStripe\Dev\Deprecation; + +/* + * @deprecated 4.13.0 Will be removed without equivalent functionality to replace it + */ class HTML4Value extends HTMLValue { + public function __construct($fragment = null) + { + Deprecation::withNoReplacement(function () { + Deprecation::notice( + '4.13.0', + 'Will be removed without equivalent functionality to replace it', + Deprecation::SCOPE_CLASS + ); + }); + parent::__construct($fragment); + } /** * @param string $content diff --git a/tests/php/View/Parsers/HTML4ValueTest.php b/tests/php/View/Parsers/HTML4ValueTest.php index 833b10047..0f8645b94 100644 --- a/tests/php/View/Parsers/HTML4ValueTest.php +++ b/tests/php/View/Parsers/HTML4ValueTest.php @@ -2,11 +2,20 @@ namespace SilverStripe\View\Tests\Parsers; +use SilverStripe\Dev\Deprecation; use SilverStripe\Dev\SapphireTest; use SilverStripe\View\Parsers\HTML4Value; class HTML4ValueTest extends SapphireTest { + protected function setUp(): void + { + parent::setUp(); + if (Deprecation::isEnabled()) { + $this->markTestSkipped('Test calls deprecated code'); + } + } + public function testInvalidHTMLSaving() { $value = new HTML4Value();