From 5486f26ac9f5fbb92ba4cb1aa9dd561c0c794718 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Tue, 10 Jun 2025 21:36:02 +0300 Subject: [PATCH] CFM-333_Email_Verifier_Enroller_Plugin-chaset-configuration (#319) * Verfication Code charset dropdown and free form * Review comment fixes * Remove placeholder * First time save render the dropdown by default. --- .../resources/locales/en_US/core_assigner.po | 12 --- .../src/Model/Table/FormatAssignersTable.php | 8 +- .../src/Model/Table/EmailVerifiersTable.php | 47 ++++++++++- .../CoreEnroller/src/config/plugin.json | 3 +- .../templates/EmailVerifiers/fields.inc | 79 +++++++++++++++++-- .../element/emailVerifiers/verify.php | 43 +++++----- app/resources/locales/en_US/enumeration.po | 12 +++ .../src/Lib/Enum/PermittedCharactersEnum.php | 4 +- app/src/Lib/Random/RandomString.php | 26 ++++-- app/src/Model/Table/VerificationsTable.php | 8 +- 10 files changed, 182 insertions(+), 60 deletions(-) rename app/{plugins/CoreAssigner => }/src/Lib/Enum/PermittedCharactersEnum.php (97%) diff --git a/app/plugins/CoreAssigner/resources/locales/en_US/core_assigner.po b/app/plugins/CoreAssigner/resources/locales/en_US/core_assigner.po index cb54c17b3..c2d707c6e 100644 --- a/app/plugins/CoreAssigner/resources/locales/en_US/core_assigner.po +++ b/app/plugins/CoreAssigner/resources/locales/en_US/core_assigner.po @@ -34,18 +34,6 @@ msgstr "Random" msgid "enumeration.CollisionModeEnum.S" msgstr "Sequential" -msgid "enumeration.PermittedCharactersEnum.AN" -msgstr "AlphaNumeric Only" - -msgid "enumeration.PermittedCharactersEnum.AD" -msgstr "AlphaNumeric and Dot, Dash, Underscore" - -msgid "enumeration.PermittedCharactersEnum.AQ" -msgstr "AlphaNumeric and Dot, Dash, Underscore, Apostrophe" - -msgid "enumeration.PermittedCharactersEnum.AL" -msgstr "Any" - msgid "error.SqlAssigners.failed" msgstr "Could not map key Identifier to target Identifier" diff --git a/app/plugins/CoreAssigner/src/Model/Table/FormatAssignersTable.php b/app/plugins/CoreAssigner/src/Model/Table/FormatAssignersTable.php index a16a26c94..8d7524460 100644 --- a/app/plugins/CoreAssigner/src/Model/Table/FormatAssignersTable.php +++ b/app/plugins/CoreAssigner/src/Model/Table/FormatAssignersTable.php @@ -29,14 +29,10 @@ namespace CoreAssigner\Model\Table; -use Cake\Datasource\ConnectionManager; -use Cake\ORM\Query; -use Cake\ORM\RulesChecker; +use App\Lib\Enum\PermittedCharactersEnum; use Cake\ORM\Table; use Cake\Validation\Validator; - use CoreAssigner\Lib\Enum\CollisionModeEnum; -use CoreAssigner\Lib\Enum\PermittedCharactersEnum; class FormatAssignersTable extends Table { use \App\Lib\Traits\AutoViewVarsTrait; @@ -83,7 +79,7 @@ public function initialize(array $config): void { ], 'permittedCharacters' => [ 'type' => 'enum', - 'class' => 'CoreAssigner.PermittedCharactersEnum' + 'class' => 'PermittedCharactersEnum' ] ]); diff --git a/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php b/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php index 0f3a08b38..03b5e2313 100644 --- a/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php +++ b/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php @@ -30,6 +30,7 @@ namespace CoreEnroller\Model\Table; use App\Lib\Enum\EnrollmentActorEnum; +use App\Lib\Enum\PermittedCharactersEnum; use App\Lib\Enum\PetitionStatusEnum; use App\Lib\Enum\SuspendableStatusEnum; use App\Lib\Enum\TableTypeEnum; @@ -122,6 +123,10 @@ public function initialize(array $config): void { 'types' => [ 'type' => 'auxiliary', 'model' => 'Types' + ], + 'permittedCharacters' => [ + 'type' => 'enum', + 'class' => 'PermittedCharactersEnum' ] ]); @@ -440,18 +445,26 @@ public function sendVerificationRequest( ]) ->first(); + [$charset, $regex] = $this->calculateVerificationCodeCharsetMode( + $emailVerifier->verification_code_charset, + $emailVerifier->verification_code_regex + ); + if (empty($pVerification)) { // Request Verification and create an associated Petition Verification $this->llog('debug', "Sending verification code to $mail for Petition " . $petition->id); + // I need to figure out the allowed characters for the code. + $verificationId = $Verifications->requestCodeForPetition( petitionId: $petition->id, mail: $mail, messageTemplateId: $emailVerifier->message_template_id, validity: $emailVerifier->request_validity, codeLength: !empty($emailVerifier->verification_code_length) ? $emailVerifier->verification_code_length : VerificationDefaultsEnum::DefaultCodeLength, - codeCharset: !empty($emailVerifier->verification_code_charset) ? $emailVerifier->verification_code_charset : VerificationDefaultsEnum::DefaultCharset, + codeCharset: $charset, + codeRegex: $regex, ); $pVerification = $PetitionVerifications->saveOrFail( @@ -474,7 +487,8 @@ public function sendVerificationRequest( messageTemplateId: $emailVerifier->message_template_id, validity: $emailVerifier->request_validity, codeLength: !empty($emailVerifier->verification_code_length) ? $emailVerifier->verification_code_length : VerificationDefaultsEnum::DefaultCodeLength, - codeCharset: !empty($emailVerifier->verification_code_charset) ? $emailVerifier->verification_code_charset : VerificationDefaultsEnum::DefaultCharset, + codeCharset: $charset, + codeRegex: $regex, verificationId: $pVerification->verification_id, ); // There's nothing to update in the Petition Verification @@ -485,6 +499,30 @@ public function sendVerificationRequest( return false; } + + /** + * Calculate verification code charset based on provided charset or permitted characters. + * Returns default charset if neither is provided. + * + * @param ?string $charset Custom charset for verification code + * @param ?string $permitted Permitted characters enum value + * @return array Resolved charset to use for verification code + * @since COmanage Registry v5.1.0 + */ + protected function calculateVerificationCodeCharsetMode( + ?string $charset, + ?string $permitted + ): array { + if (empty($charset) && empty($permitted)) { + return [VerificationDefaultsEnum::DefaultCharset, null]; + } + if (!empty($permitted)) { + return [null, PermittedCharactersEnum::getPermittedCharacters(enum: $permitted)]; + } + + return [$charset, null]; + } + /** * Set validation rules. * @@ -531,6 +569,11 @@ public function validationDefault(Validator $validator): Validator { ]); $validator->allowEmptyString('verification_code_charset'); + $validator->add('verification_code_regex', [ + 'content' => ['rule' => ['inList', PermittedCharactersEnum::getConstValues()]] + ]); + $validator->allowEmptyString('verification_code_regex'); + $validator ->add('verification_code_length', 'content', [ 'rule' => 'isInteger', diff --git a/app/plugins/CoreEnroller/src/config/plugin.json b/app/plugins/CoreEnroller/src/config/plugin.json index ed2552daf..35efbcfe6 100644 --- a/app/plugins/CoreEnroller/src/config/plugin.json +++ b/app/plugins/CoreEnroller/src/config/plugin.json @@ -46,7 +46,8 @@ "request_validity": { "type": "integer" }, "enable_blockonfailure": { "type": "boolean" }, "verification_code_length": { "type": "integer" }, - "verification_code_charset": { "type": "string", "size": 64 } + "verification_code_charset": { "type": "string", "size": 64 }, + "verification_code_regex": { "type": "string", "size": 2 } }, "indexes": { "email_verifiers_i1": { "columns": [ "enrollment_flow_step_id" ] }, diff --git a/app/plugins/CoreEnroller/templates/EmailVerifiers/fields.inc b/app/plugins/CoreEnroller/templates/EmailVerifiers/fields.inc index 1bd7a2c6c..5cf5e674d 100644 --- a/app/plugins/CoreEnroller/templates/EmailVerifiers/fields.inc +++ b/app/plugins/CoreEnroller/templates/EmailVerifiers/fields.inc @@ -51,15 +51,80 @@ print $this->element('form/listItem', [ ]] ]); -print $this->element('form/listItem', [ - 'arguments' => [ - 'fieldName' => 'verification_code_charset', - 'fieldOptions' => [ - 'required' => false, - 'placeholder' => $defaultValues['DefaultCharset'] - ]] +// Textbox for "Other" +$otherField = $this->Form->control('verification_code_charset', [ + 'type' => 'text', + 'label' => false, + 'id' => 'verification-code-charset', + 'style' => !empty($vv_obj?->verification_code_charset) ? '' : 'display: none;', + 'required' => false, ]); +$checked = !empty($vv_obj?->verification_code_charset) ? 'checked' : ''; +$otherLabel = __d('field','other.value'); +$beforeField = << +
+ + +
+ +$otherField +OTHER; + +$afterField = <<<'JS' + +JS; + +$regexDisplayFirstTime = empty($vv_obj?->verification_code_charset) && empty($vv_obj?->verification_code_regex); +print $this->element('form/listItem', [ + 'arguments' => [ + 'fieldName' => 'verification_code_regex', + 'fieldLabel' => __d('core_enroller','field.EmailVerifiers.verification_code_charset'), + 'fieldOptions' => [ + 'id' => 'verification-code-regex', + 'required' => false, + 'empty' => true, + 'style' => $regexDisplayFirstTime || !empty($vv_obj?->verification_code_regex) ? '' : 'display: none;', + ], + 'fieldType' => 'select', + 'fieldSelectOptions' => $permittedCharacters, + ], + 'beforeField' => $beforeField, + 'afterField' => $afterField, + ] +); + print $this->element('form/listItem', [ 'arguments' => [ 'fieldName' => 'verification_code_length', diff --git a/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php b/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php index 63d5ecd30..56cd5d5e8 100644 --- a/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php +++ b/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php @@ -124,26 +124,27 @@ history.pushState(null, null, document.URL); }); - $('#code').bind('keypress', function (event) { - if (event.charCode === 13) { - $("#verification-code-form").submit(); - } else { - // Allow for regular characters and include these special few: - // comma, period, explanation point, new line - var regex = new RegExp("^[a-zA-Z0-9\-]+$"); - var key = String.fromCharCode(!event.charCode ? event.which : event.charCode); - if (!regex.test(key)) { - event.preventDefault(); - return false; - } - } - }); - - $('#code').on('keyup', function() { - $(this).val (function () { - return this.value.toUpperCase(); - }).trigger('change'); - }) - }); + // XXX Keep for now. This is the old way to handle the enter key + // $('#code').bind('keypress', function (event) { + // if (event.charCode === 13) { + // $("#verification-code-form").submit(); + // } else { + // // Allow for regular characters and include these special few: + // // comma, period, explanation point, new line + // var regex = new RegExp("^[a-zA-Z0-9\-]+$"); + // var key = String.fromCharCode(!event.charCode ? event.which : event.charCode); + // if (!regex.test(key)) { + // event.preventDefault(); + // return false; + // } + // } + // }); + // + // $('#code').on('keyup', function() { + // $(this).val (function () { + // return this.value.toUpperCase(); + // }).trigger('change'); + // }) + // }); diff --git a/app/resources/locales/en_US/enumeration.po b/app/resources/locales/en_US/enumeration.po index 6dc5f5ea0..efffd05dc 100644 --- a/app/resources/locales/en_US/enumeration.po +++ b/app/resources/locales/en_US/enumeration.po @@ -405,6 +405,18 @@ msgstr "Error Landing" msgid "PageContextEnum.G" msgstr "General" +msgid "PermittedCharactersEnum.AL" +msgstr "Any" + +msgid "PermittedCharactersEnum.AN" +msgstr "AlphaNumeric Only" + +msgid "PermittedCharactersEnum.AD" +msgstr "AlphaNumeric and Dot, Dash, Underscore" + +msgid "PermittedCharactersEnum.AQ" +msgstr "AlphaNumeric and Dot, Dash, Underscore, Apostrophe" + msgid "PermittedNameFieldsEnum.given,family" msgstr "Given, Family" diff --git a/app/plugins/CoreAssigner/src/Lib/Enum/PermittedCharactersEnum.php b/app/src/Lib/Enum/PermittedCharactersEnum.php similarity index 97% rename from app/plugins/CoreAssigner/src/Lib/Enum/PermittedCharactersEnum.php rename to app/src/Lib/Enum/PermittedCharactersEnum.php index 6abec1b9e..5ce014719 100644 --- a/app/plugins/CoreAssigner/src/Lib/Enum/PermittedCharactersEnum.php +++ b/app/src/Lib/Enum/PermittedCharactersEnum.php @@ -27,9 +27,7 @@ declare(strict_types = 1); -namespace CoreAssigner\Lib\Enum; - -use App\Lib\Enum\StandardEnum; +namespace App\Lib\Enum; class PermittedCharactersEnum extends StandardEnum { const AlphaNumeric = 'AN'; diff --git a/app/src/Lib/Random/RandomString.php b/app/src/Lib/Random/RandomString.php index 4038fc43b..ba183990b 100644 --- a/app/src/Lib/Random/RandomString.php +++ b/app/src/Lib/Random/RandomString.php @@ -29,6 +29,7 @@ namespace App\Lib\Random; +use App\Lib\Enum\PermittedCharactersEnum; use Random\RandomException; class RandomString { @@ -102,17 +103,32 @@ public static function generateCode(): string { * @since COmanage Registry v5.0.0 */ - public static function generateToken(int $length = 16, string $allowedCharset = null): string - { + public static function generateToken( + int $length = 16, + string $allowedCharset = null, + string $regex = null, // Permitted + ): string { // Unlike App Keys, tokens don't have restrictions on characters. $chars = $allowedCharset ?? 'abcdefghijklmnopqrstuvwxyz01234567890'; $token = ""; - $allowedCharsetLength = strlen($chars) - 1; - for($i = 0;$i < $length;$i++) { - $token .= $chars[random_int(0, $allowedCharsetLength)]; + if ($regex !== null) { + // We allow the characters that match the regex. + while(strlen($token) < $length) { + $ascii = random_int(32, 126); // 32 is space, 126 is '~' + $randomChar = chr($ascii); + if (preg_match('/'. $regex . '/', $randomChar) === 1) { + $token .= $randomChar; + + } + } + } else { + $allowedCharsetLength = strlen($chars) - 1; + for($i = 0;$i < $length;$i++) { + $token .= $chars[random_int(0, $allowedCharsetLength)]; + } } return $token; diff --git a/app/src/Model/Table/VerificationsTable.php b/app/src/Model/Table/VerificationsTable.php index 07ab7b117..d17867d61 100644 --- a/app/src/Model/Table/VerificationsTable.php +++ b/app/src/Model/Table/VerificationsTable.php @@ -248,7 +248,8 @@ protected function recordHistory( * @param int $messageTemplateId Message Template ID * @param int $validity Request validity, in minutes * @param int $codeLength - * @param string $codeCharset + * @param string|null $codeCharset + * @param string|null $codeRegex * @param int|null $verificationId If set, resend Verification for this request * @return int Verification ID * @throws RandomException @@ -261,11 +262,12 @@ public function requestCodeForPetition( int $messageTemplateId, int $validity, int $codeLength, - string $codeCharset, + ?string $codeCharset, + ?string $codeRegex, int $verificationId = null ): int { // First generate a new code - $code = RandomString::generateToken($codeLength, $codeCharset); + $code = RandomString::generateToken($codeLength, $codeCharset, $codeRegex); $expiry = date('Y-m-d H:i:s', time() + ($validity * 60)); $verification = null;