Skip to content

Resend Confirmation Code #308

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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');
}