Skip to content

Commit

Permalink
Email Verificiation.Charset and code length configuration. Block user…
Browse files Browse the repository at this point in the history
… on failed attempt.
  • Loading branch information
Ioannis committed Apr 28, 2025
1 parent 576f8c2 commit 07932a3
Show file tree
Hide file tree
Showing 21 changed files with 498 additions and 80 deletions.
39 changes: 39 additions & 0 deletions app/plugins/CoreEnroller/resources/locales/en_US/core_enroller.po
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ msgstr "{0,plural,=1{Petition Identifier} other{Petition Identifiers}}"
msgid "controller.PetitionVerifications"
msgstr "{0,plural,=1{Petition Verification} other{Petition Verifications}}"

msgid "enumeration.VerificationDefaultsEnum.234679CDFGHJKLMNPQRTVWXZ"
msgstr "DefaultCharset"

msgid "enumeration.VerificationDefaultsEnum.8"
msgstr "DefaultCodeLength"

msgid "enumeration.VerificationDefaultsEnum.60"
msgstr "DefaultVerificationValidity"

msgid "enumeration.VerificationModeEnum.0"
msgstr "None"

Expand All @@ -70,6 +79,24 @@ msgstr "Requested address is not a valid candidate"
msgid "error.EmailVerifiers.minimum"
msgstr "The required number of verified Email Addresses has not been met"

msgid "error.EmailVerifiers.code_length.content"
msgstr "The code length must be a numeric value."

msgid "error.EmailVerifiers.code_length.comparison_max"
msgstr "Code length must not be less than 4 characters."

msgid "error.EmailVerifiers.code_length.step_four"
msgstr "Code length must be increased by 4 characters, e.g. 8, 12, 16, 20."

msgid "error.EmailVerifiers.code_length.comparison_less"
msgstr "Code length must not exceed 20 characters."

msgid "error.EmailVerifiers.verification_code_charset.content"
msgstr "Letters and numbers only."

msgid "error.EmailVerifiers.verification_code_charset.is_upper_case"
msgstr "Charset must consist of UPPER case characters only."

msgid "error.EmailVerifiers.verified"
msgstr "Requested address is already verified"

Expand Down Expand Up @@ -157,6 +184,18 @@ msgstr "Request Validity"
msgid "field.EmailVerifiers.request_validity.desc"
msgstr "Duration, in minutes, of the verification request before it expires"

msgid "field.EmailVerifiers.verification_code_charset"
msgstr "Verification Code Character Set"

msgid "field.EmailVerifiers.verification_code_charset.desc"
msgstr "Set of characters for generating the verification code. Numbers and uppercase letters only."

msgid "field.EmailVerifiers.verification_code_length"
msgstr "Verification Code Length"

msgid "field.EmailVerifiers.verification_code_length.desc"
msgstr "Set the verification code length. Default length is 8."

msgid "field.EnrollmentAttributes.address_required_fields"
msgstr "Required Address Fields"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,18 +183,26 @@ public function dispatch(string $id) {

$this->Flash->error(__d('core_enroller', 'error.EmailVerifiers.verified'));
} else {
$PetitionVerifications = TableRegistry::getTableLocator()->get('CoreEnroller.PetitionVerifications');
$pVerification = $PetitionVerifications->getPetitionVerification($petition->id, $mail, false);

// Tell dispatch.inc to render a verification form
$this->set('vv_verify_address', $mail);
$this->set('vv_attempts_count', $pVerification->attempts_count ?? 0);

if($this->request->is('post')) {
$PetitionVerifications = TableRegistry::getTableLocator()->get('CoreEnroller.PetitionVerifications');

// We're back with the code. Note many parameters (but not code) will be in
// both the URL and the post body because of how dispatch.php sets up
// FormHelper.

$code = $this->requestParam('code');
// Strip any dashes from the code
$code = str_replace('-', '', $code);

try {
$PetitionVerifications->verifyCode(
$petition->id,
$petition->id,
$cfg->enrollment_flow_step_id,
$mail,
$code
Expand Down Expand Up @@ -234,9 +242,6 @@ public function dispatch(string $id) {

$this->EmailVerifiers->sendVerificationRequest($cfg, $petition, $mail);
}

// Tell dispatch.inc to render a verification form
$this->set('vv_verify_address', $mail);
}
} elseif($op == 'finish') {
if($minimumMet) {
Expand Down
38 changes: 38 additions & 0 deletions app/plugins/CoreEnroller/src/Lib/Enum/VerificationDefaultsEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
/**
* COmanage Registry Verification Defaults Enum
*
* Portions licensed to the University Corporation for Advanced Internet
* Development, Inc. ("UCAID") under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* UCAID licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @link https://www.internet2.edu/comanage COmanage Project
* @package registry-plugins
* @since COmanage Registry v5.2.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

declare(strict_types = 1);

namespace CoreEnroller\Lib\Enum;

use App\Lib\Enum\StandardEnum;

class VerificationDefaultsEnum extends StandardEnum {
const DefaultCharset = '234679CDFGHJKLMNPQRTVWXZ';
const DefaultCodeLength = 8;
const DefaultVerificationValidity = 60;
}
73 changes: 59 additions & 14 deletions app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,14 @@
use App\Lib\Enum\EnrollmentActorEnum;
use App\Lib\Enum\PetitionStatusEnum;
use App\Lib\Enum\SuspendableStatusEnum;
use App\Lib\Enum\TableTypeEnum;
use App\Lib\Util\StringUtilities;
use App\Model\Entity\Petition;
use Cake\Datasource\ConnectionManager;
use Cake\Datasource\EntityInterface;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Validation\Validator;
use CoreEnroller\Lib\Enum\VerificationModeEnum;
use CoreEnroller\Lib\Enum\VerificationDefaultsEnum;
use CoreEnroller\Model\Entity\EmailVerifier;

class EmailVerifiersTable extends Table {
Expand Down Expand Up @@ -69,7 +67,7 @@ public function initialize(array $config): void {
$this->addBehavior('Log');
$this->addBehavior('Timestamp');

$this->setTableType(\App\Lib\Enum\TableTypeEnum::Configuration);
$this->setTableType(TableTypeEnum::Configuration);

// Define associations
$this->belongsTo('EnrollmentFlowSteps');
Expand Down Expand Up @@ -108,6 +106,10 @@ public function initialize(array $config): void {
'type' => 'enum',
'class' => 'CoreEnroller.VerificationModeEnum'
],
'defaults' => [
'type' => 'enum',
'class' => 'CoreEnroller.VerificationDefaultsEnum'
],
'messageTemplates' => [
'type' => 'select',
'model' => 'MessageTemplates',
Expand Down Expand Up @@ -444,10 +446,12 @@ public function sendVerificationRequest(
$this->llog('debug', "Sending verification code to $mail for Petition " . $petition->id);

$verificationId = $Verifications->requestCodeForPetition(
$petition->id,
$mail,
$emailVerifier->message_template_id,
$emailVerifier->request_validity
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,
);

$pVerification = $PetitionVerifications->saveOrFail(
Expand All @@ -465,11 +469,13 @@ public function sendVerificationRequest(
$this->llog('debug', "Sending replacement verification code to $mail for Petition " . $petition->id);

$verificationId = $Verifications->requestCodeForPetition(
$petition->id,
$mail,
$emailVerifier->message_template_id,
$emailVerifier->request_validity,
$pVerification->verification_id
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,
verificationId: $pVerification->verification_id,
);
// There's nothing to update in the Petition Verification

Expand Down Expand Up @@ -510,6 +516,45 @@ public function validationDefault(Validator $validator): Validator {
]);
$validator->notEmptyString('request_validity');

$validator
->add('verification_code_charset', [
'content' => [
'rule' => 'alphaNumeric',
'last' => true,
'message' => __d('core_enroller', 'error.EmailVerifiers.verification_code_charset.content'),
],
'is_upper_case' => [
'rule' => fn($value, $context) => $value === strtoupper($value),
'message' => __d('core_enroller', 'error.EmailVerifiers.verification_code_charset.is_upper_case'),
'last' => true,
],
]);
$validator->allowEmptyString('verification_code_charset');

$validator
->add('verification_code_length', 'content', [
'rule' => 'isInteger',
'last' => true,
'message' => __d('core_enroller', 'error.EmailVerifiers.code_length.content'),
])
->add('verification_code_length', 'comparison_max', [
'rule' => ['comparison', '>=', 1],
'last' => true,
'message' => __d('core_enroller', 'error.EmailVerifiers.code_length.comparison_max'),
])
->add('verification_code_length', 'comparison_less', [
'rule' => ['comparison', '<=', 20],
'last' => true,
'message' => __d('core_enroller', 'error.EmailVerifiers.code_length.comparison_less'),
])
->add('verification_code_length', 'step_four', [
'rule' => ['validateIncreaseStep', 4],
'provider' => 'table',
'last' => true,
'message' => __d('core_enroller', 'error.EmailVerifiers.code_length.step_four'),
]);
$validator->allowEmptyString('verification_code_length');

return $validator;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,11 @@ public function verifyCode(int $petitionId, int $enrollmentFlowStepId, string $m
// Find the PetitionVerification for the requested petition and address,
// then use the verification ID to process the code.

$pVerification = $this->find()
->where([
'petition_id' => $petitionId,
'mail' => $mail
])
->firstOrFail();

$pVerification = $this->getPetitionVerification($petitionId, $mail);
// Increase the attempt counter and save regardless of the code validation
$pVerification->attempts_count = ($pVerification->attempts_count ?? 0) + 1;
$this->save($pVerification);

// This will throw an error on failure
$this->Verifications->verifyCode($pVerification->verification_id, $code);

Expand Down Expand Up @@ -202,4 +200,25 @@ public function validationDefault(Validator $validator): Validator {

return $validator;
}


/**
* Retrieve a PetitionVerification entity based on a petition ID and email address.
*
* @since COmanage Registry v5.2.0
* @param integer $petitionId Petition ID
* @param string $mail Email Address associated with the PetitionVerification
* @param bool $strict Whether to throw an error if no result is found (default: true)
* @return \CoreEnroller\Model\Entity\PetitionVerification|null PetitionVerification entity if found, or null
* @throws \Cake\Datasource\Exception\RecordNotFoundException If $strict is true and no result is found
*/
public function getPetitionVerification(int $petitionId, string $mail, bool $strict = true): ?PetitionVerification
{
$query = $this->find()
->where([
'petition_id' => $petitionId,
'mail' => $mail
]);
return $strict ? $query->firstOrFail() : $query->first();
}
}
5 changes: 4 additions & 1 deletion app/plugins/CoreEnroller/src/config/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@
"enrollment_flow_step_id": {},
"mode": { "type": "string", "size": 2 },
"message_template_id": {},
"request_validity": { "type": "integer" }
"request_validity": { "type": "integer" },
"verification_code_length": { "type": "integer" },
"verification_code_charset": { "type": "string", "size": 64 }
},
"indexes": {
"email_verifiers_i1": { "columns": [ "enrollment_flow_step_id" ] },
Expand Down Expand Up @@ -151,6 +153,7 @@
"id": {},
"petition_id": {},
"mail": {},
"attempts_count": { "type": "integer" },
"verification_id": { "type": "integer", "foreignkey": { "table": "verifications", "column": "id" } }
},
"indexes": {
Expand Down
51 changes: 38 additions & 13 deletions app/plugins/CoreEnroller/templates/EmailVerifiers/fields.inc
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,45 @@
*/

// This view only supports edit
if($vv_action == 'edit') {
foreach(['mode',
'message_template_id'
] as $field) {
print $this->element('form/listItem', [
'arguments' => ['fieldName' => $field]
]);
}
if($vv_action !== 'edit') {
return;
}

$defaultValues = array_combine(
array_values($defaults),
array_keys($defaults)
);

foreach(['mode',
'message_template_id'
] as $field) {
print $this->element('form/listItem', [
'arguments' => [
'fieldName' => 'request_validity',
'fieldOptions' => [
'default' => 60
]]
'arguments' => ['fieldName' => $field]
]);
}

print $this->element('form/listItem', [
'arguments' => [
'fieldName' => 'request_validity',
'fieldOptions' => [
'placeholder' => $defaultValues['DefaultVerificationValidity']
]]
]);

print $this->element('form/listItem', [
'arguments' => [
'fieldName' => 'verification_code_charset',
'fieldOptions' => [
'required' => false,
'placeholder' => $defaultValues['DefaultCharset']
]]
]);

print $this->element('form/listItem', [
'arguments' => [
'fieldName' => 'verification_code_length',
'fieldOptions' => [
'required' => false,
'placeholder' => $defaultValues['DefaultCodeLength']
]]
]);
Loading

0 comments on commit 07932a3

Please sign in to comment.