Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Resend Confirmation Code (#308)
Ioannis committed Mar 3, 2025
1 parent 435fd31 commit 595c5e8
Showing 13 changed files with 683 additions and 139 deletions.
53 changes: 53 additions & 0 deletions app/plugins/CoreEnroller/config/routes.php
@@ -0,0 +1,53 @@
<?php
/**
* ApiSource plugin specific routes.
*
* 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.0.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

use Cake\Routing\Route\DashedRoute;

// In general, we're probably trying to set up API routes if we're doing
// something within a plugin, but not necessarily. API routes are a subset
// of Cake routes, so either can be specified here.

// Core Enroller


$routes->plugin(
'CoreEnroller',
['path' => '/core-enroller/'],
function ($routes) {
$routes->setRouteClass(DashedRoute::class);

$routes->get(
'email-verifiers/resend',
[
'plugin' => 'CoreEnroller',
'controller' => 'EmailVerifiers',
'action' => 'resend',
])
->setPass(['id'])
->setPatterns(['id' => '[0-9]+']);
}
);
22 changes: 20 additions & 2 deletions app/plugins/CoreEnroller/resources/locales/en_US/core_enroller.po
@@ -98,7 +98,22 @@ msgid "information.EmailVerifiers.A"
msgstr "The following email addresses have been found in this Petition. You must verify all of them in order to proceed to the next Enrollment Step."

msgid "information.EmailVerifiers.code_sent"
msgstr "A code has been sent to {0}. Please enter it below. You may also request a new code if you haven't received it after a few minutes, or cancel verification and return to the list of available Email Addresses."
msgstr "A code has been sent to <strong>{0}</strong>. Please enter it below. You may also request a new code if you haven't received it after a few minutes, or cancel verification and return to the list of available Email Addresses."

msgid "information.EmailVerifiers.resend-pre-text"
msgstr "Didn't receive the code?"

msgid "information.EmailVerifiers.resend"
msgstr "Resend"

msgid "information.EmailVerifiers.sending"
msgstr "Sending"

msgid "information.EmailVerifiers.success"
msgstr "New Code Submitted!"

msgid "information.EmailVerifiers.abort"
msgstr "Abort"

msgid "field.AttributeCollectors.valid_through.default.after.desc"
msgstr "Days After Finalization"
@@ -257,4 +272,7 @@ msgid "result.InvitationAccepters.declined"
msgstr "Invitation Declined at {0}"

msgid "result.InvitationAccepters.none"
msgstr "No response to invitation yet"
msgstr "No response to invitation yet"

msgid "op.EmailVerifiers.verify"
msgstr "Verify Email"
@@ -29,11 +29,13 @@

namespace CoreEnroller\Controller;

use Cake\ORM\TableRegistry;
use App\Controller\StandardEnrollerController;
use App\Lib\Enum\PetitionStatusEnum;
use App\Lib\Util\StringUtilities;
use Cake\Http\Exception\BadRequestException;
use Cake\ORM\TableRegistry;
use CoreEnroller\Lib\Enum\VerificationModeEnum;
use \App\Lib\Enum\HttpStatusCodesEnum;

class EmailVerifiersController extends StandardEnrollerController {
public $paginate = [
@@ -60,9 +62,58 @@ public function beforeRender(\Cake\Event\EventInterface $event) {
$this->set('vv_bc_parent_primarykey', $this->EmailVerifiers->EnrollmentFlowSteps->getPrimaryKey());
}

if ($this->getRequest()->getQuery("op") == "verify" || $this->getRequest()->getQuery("op") == "index") {
// This will suppress the default behavior. By default, we print the submit button in the
// unorderedList.php element. But for the verify view we want to override and customize
$this->set('suppress_submit', true);
}

return parent::beforeRender($event);
}

/**
* Resend the email verification request.
*
* @param string $id Email Verifier ID
* @throws BadRequestException If the request is not AJAX
* @throws \InvalidArgumentException If required query parameters are missing
* @return void
* @since COmanage Registry v5.1.0
*/
public function resend($id)
{

$this->viewBuilder()->setClassName('Json');

if (!$this->getRequest()->is('ajax')) {
throw new BadRequestException(__('Bad Request'));
}

if (!$this->getRequest()->getQuery('petition_id') || !$this->getRequest()->getQuery('m')) {
throw new \InvalidArgumentException(__('error', 'invalid.request'));
}

// Generate a Verification request and send it
$Petitions = TableRegistry::getTableLocator()->get('Petitions');
$petition = $Petitions->get($this->getRequest()->getQuery('petition_id'));
$cfg = $this->EmailVerifiers->get($id);
$mail = StringUtilities::urlbase64decode($this->requestParam('m'));
$status = $this->EmailVerifiers->sendVerificationRequest($cfg, $petition, $mail, true);

if ($status) {
return $this->response
->withType('application/json')
->withStatus(HttpStatusCodesEnum::HTTP_OK)
->withStringBody(json_encode(['status' => 'ok']));
}


return $this->response
->withType('application/json')
->withStatus(HttpStatusCodesEnum::HTTP_INTERNAL_SERVER_ERROR)
->withStringBody(json_encode(['status' => 'failed']));
}

/**
* Dispatch an Enrollment Flow Step.
*
55 changes: 36 additions & 19 deletions app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php
@@ -85,7 +85,7 @@ public function initialize(array $config): void {

$this->setPrimaryLink('enrollment_flow_step_id');
$this->setRequiresCO(true);
$this->setAllowLookupPrimaryLink(['dispatch', 'display']);
$this->setAllowLookupPrimaryLink(['dispatch', 'display', 'resend']);

// All the tabs share the same configuration in the ModelTable file
$this->setTabsConfig(
@@ -112,6 +112,14 @@ public function initialize(array $config): void {
'type' => 'select',
'model' => 'MessageTemplates',
'where' => ['context' => \App\Lib\Enum\MessageTemplateContextEnum::Verification]
],
'cosettings' => [
'type' => 'auxiliary',
'model' => 'CoSettings'
],
'types' => [
'type' => 'auxiliary',
'model' => 'Types'
]
]);

@@ -122,6 +130,7 @@ public function initialize(array $config): void {
'dispatch' => true,
'display' => true,
'edit' => ['platformAdmin', 'coAdmin'],
'resend' => true,
'view' => ['platformAdmin', 'coAdmin']
],
// Actions that operate over a table (ie: do not require an $id)
@@ -412,8 +421,10 @@ public function prepare(
public function sendVerificationRequest(
EmailVerifier $emailVerifier,
Petition $petition,
string $mail
) {
string $mail,
bool $resend = false,
): bool
{
// First check if there is already an existing Petition Verification.
// If so, use that to get the existing Verification.

@@ -427,21 +438,7 @@ public function sendVerificationRequest(
])
->first();

if(!empty($pVerification)) {
// Request a new code

$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
);

// There's nothing to update in the Petition Verification
} else {
if (empty($pVerification)) {
// Request Verification and create an associated Petition Verification

$this->llog('debug', "Sending verification code to $mail for Petition " . $petition->id);
@@ -458,8 +455,28 @@ public function sendVerificationRequest(
'petition_id' => $petition->id,
'mail' => $mail,
'verification_id' => $verificationId
]));
]));
return true;
}

if ($resend) {
// Request a new code

$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
);
// There's nothing to update in the Petition Verification

return true;
}

return false;
}

/**
130 changes: 15 additions & 115 deletions app/plugins/CoreEnroller/templates/EmailVerifiers/dispatch.inc
@@ -27,121 +27,21 @@

declare(strict_types = 1);

use CoreEnroller\Lib\Enum\VerificationModeEnum;
use App\Lib\Util\StringUtilities;

print $this->element('flash', []);

// This view is intended to work with dispatch
if($vv_action == 'dispatch') {
if($vv_op == 'index') {
// Render the list of known email addresses and their verification statuses.
// The configuration drives how many email addresses are required to complete this step.

print '<p>';

if($vv_all_done) {
print __d('core_enroller', 'information.EmailVerifiers.done');
} else {
switch($vv_config->mode) {
case VerificationModeEnum::All:
print __d('core_enroller', 'information.EmailVerifiers.A');
break;
case VerificationModeEnum::None:
print __d('core_enroller', 'information.EmailVerifiers.0');
break;
case VerificationModeEnum::One:
if($vv_minimum_met) {
print __d('core_enroller', 'information.EmailVerifiers.1.met');
} else {
print __d('core_enroller', 'information.EmailVerifiers.1.none');
}
break;
}
}

print '</p>';

print '
<table id="verifications-table" class="index-table list-mode">
<thead>
<tr>
<th>' . __d('controller', 'EmailAddresses', [1]) . '</th>
<th>' . __d('field', 'status') . '</th>
</tr>
</thead>
</tbody>
';

foreach(array_keys($vv_email_addresses) as $addr) {
$verified = isset($vv_verified_addresses[$addr]) && $vv_verified_addresses[$addr];

$button = "";

if(!$verified) {
// We're already in a form here, so we need to use a GET URL to not mess things up.
// This also means we need to manually insert the token and petition ID, which is
// a bit duplicative with templates/Standard/dispatch.php

$url = [
'plugin' => 'CoreEnroller',
'controller' => 'email_verifiers',
'action' => 'dispatch',
$vv_config->id,
'?' => [
'op' => 'verify',
'petition_id' => $vv_petition->id,
// We base64 encode the address partly to not have bare email addresses in URLs
// and partly to avoid special characters (like dots) messing up the URL
'm' => StringUtilities::urlbase64encode($addr)
]
];

if(isset($vv_token_ok) && $vv_token_ok && !empty($vv_petition->token)) {
$url['?']['token'] = $vv_petition->token;
}

$button = $this->Html->link(__d('operation', 'verify'), $url);
}

print '
<tr>
<td>' . $addr . '</td>
<td>' . __d('result', ($verified ? 'verified' : 'verified.not')) . $button . '</td>
</tr>
';
}

print '
</tbody>
</table>
';

if($vv_minimum_met) {
$this->Field->enableFormEditMode();

print $this->Form->hidden('op', ['default' => 'finish']);
}
} elseif($vv_op == 'verify') {
if(!empty($vv_verify_address)) {
// Render a form prompting for the code that was sent to the Enrollee

print __d('core_enroller', 'information.EmailVerifiers.code_sent', [$vv_verify_address]);

$this->Field->enableFormEditMode();

print $this->Form->hidden('op', ['default' => 'verify']);
print $this->Form->hidden('co_id', ['default' => $vv_cur_co->id]);
print $this->Form->hidden('m', ['default' => StringUtilities::urlbase64encode($vv_verify_address)]);

print $this->element('form/listItem', [
'arguments' => [
'fieldName' => 'code',
'fieldLabel' => "Code", //__d('field', 'mail')
'fieldOptions' => [
'required' => true
]
]]);
}
}
}
if ($vv_action !== 'dispatch') {
return;
}

if ($vv_op == 'index') {
$this->set('vv_include_cancel', false);
$this->set('vv_submit_button_label', __d('operation', 'finish'));
print $this->element('CoreEnroller.emailVerifiers/list');
} elseif ($vv_op == 'verify') {
$this->set('vv_submit_button_label', __d('core_enroller', 'op.EmailVerifiers.verify'));
$this->set('vv_include_cancel', true);
print $this->element('CoreEnroller.emailVerifiers/verify');
} else {
print __d('error', 'something.went.wrong');
}

0 comments on commit 595c5e8

Please sign in to comment.