From d0207b105ea78a00602676599c5f6bed53c18441 Mon Sep 17 00:00:00 2001 From: Michiel Uitdehaag Date: Wed, 29 Aug 2018 12:34:23 +0200 Subject: [PATCH] This fix allows skipping the confirmation stage if all the email addresses present are already considered verified. Also, this fix allows verifying a CoPerson email address if such an address was entered during enrollment and the OrgIdentity email address was already considered verified. --- app/Controller/CoInvitesController.php | 8 +- app/Controller/CoPetitionsController.php | 46 ++++++--- app/Model/CoInvite.php | 73 +++------------ app/Model/CoPetition.php | 113 ++++++++++++++++++----- 4 files changed, 137 insertions(+), 103 deletions(-) diff --git a/app/Controller/CoInvitesController.php b/app/Controller/CoInvitesController.php index 14d41d0e4..bd2c26990 100644 --- a/app/Controller/CoInvitesController.php +++ b/app/Controller/CoInvitesController.php @@ -371,13 +371,7 @@ protected function process_invite($inviteid, $confirm, $loginIdentifier=null) { // supported enrollment flow involves a new Org Identity for the CO, so // there won't be an existing CO Person identity linked to use instead. // At some point (ie: additional Role enrollment; CO-310) this will change. - - $token = Security::generateAuthKey(); - - $this->CoInvite->CoPetition->id = $invite['CoPetition']['id']; - $this->CoInvite->CoPetition->saveField('enrollee_token', $token); - - $redirect['token'] = $token; + $redirect['token'] = $this->CoInvite->CoPetition->ensureEnrolleeToken($invite['CoPetition']['id']); $this->redirect($redirect); } elseif(!empty($invite['CoInvite']['email_address_id'])) { diff --git a/app/Controller/CoPetitionsController.php b/app/Controller/CoPetitionsController.php index ec80f2726..bc9cb0712 100644 --- a/app/Controller/CoPetitionsController.php +++ b/app/Controller/CoPetitionsController.php @@ -1729,31 +1729,49 @@ protected function execute_sendApproverNotification($id) { */ protected function execute_sendConfirmation($id) { - $this->CoPetition->sendConfirmation($id, $this->Session->read('Auth.User.co_person_id')); + + $ea = $this->CoPetition->needConfirmation($id); + if(!empty($ea)) { + + $this->CoPetition->sendConfirmation($id, $ea, $this->Session->read('Auth.User.co_person_id')); - $this->CoPetition->updateStatus($id, + $this->CoPetition->updateStatus($id, PetitionStatusEnum::PendingConfirmation, $this->Session->read('Auth.User.co_person_id')); - // The step is done + // The step is done - $debug = Configure::read('debug'); + $debug = Configure::read('debug'); - if(!$debug) { - $this->redirect($this->generateDoneRedirect('sendConfirmation', $id)); - } else { - // We need to populate the view var to render the debug link - $coInviteId = $this->CoPetition->field('co_invite_id', + if(!$debug) { + $this->redirect($this->generateDoneRedirect('sendConfirmation', $id)); + } else { + // We need to populate the view var to render the debug link + $coInviteId = $this->CoPetition->field('co_invite_id', array('CoPetition.id' => $id)); - if($coInviteId) { - $args = array(); - $args['conditions']['CoInvite.id'] = $coInviteId; - $args['contain'] = false; + if($coInviteId) { + $args = array(); + $args['conditions']['CoInvite.id'] = $coInviteId; + $args['contain'] = false; - $this->set('vv_co_invite', $this->CoPetition->CoInvite->find('first', $args)); + $this->set('vv_co_invite', $this->CoPetition->CoInvite->find('first', $args)); + } } } + else { + + // there are no unconfirmed addresses left, directly proceed to processConfirmation + $coPersonId = $this->CoPetition->field('enrollee_co_person_id', array('CoPetition.id' => $id)); + $this->CoPetition->updateStatus($id, PetitionStatusEnum::Confirmed, $coPersonId); + + $redirect = $this->generateDoneRedirect('processConfirmation', $id); + + // we need to switch from PetitionerToken to EnrolleeToken, which is normally done in the + // process of sending an invite and confirming it + $redirect['token'] = $this->CoPetition->ensureEnrolleeToken($id); + $this->redirect($redirect); + } } /** diff --git a/app/Model/CoInvite.php b/app/Model/CoInvite.php index 8cd6471c3..998db53e8 100644 --- a/app/Model/CoInvite.php +++ b/app/Model/CoInvite.php @@ -106,7 +106,7 @@ public function processReply($inviteId, $confirm, $loginIdentifier=null) { // Check invite validity if(time() < strtotime($invite['CoInvite']['expires'])) { - if($verifyEmail) { + if($verifyEmail && $confirm) { // Verifying an email address try { @@ -121,12 +121,9 @@ public function processReply($inviteId, $confirm, $loginIdentifier=null) { $dbc->rollback(); throw new RuntimeException($e->getMessage()); } - } elseif(isset($invite['CoPetition']['id'])) { - // Before we can delete the invitation, we need to unlink it from the petition - - $this->CoPetition->id = $invite['CoPetition']['id']; - $this->CoPetition->saveField('co_invite_id', null); - } else { + } + + if(!isset($invite['CoPetition']['id'])) { // Default (ie: non-enrollment flow) behavior: update CO Person $this->CoPerson->id = $invite['CoPerson']['id']; @@ -137,58 +134,7 @@ public function processReply($inviteId, $confirm, $loginIdentifier=null) { } } - // Mark the email address associated with this invite as verified. - - if($confirm) { - // We're actually verifying an org identity email address even though we're - // getting to the EmailAddress object via CoPerson - - $orgId = null; - - if(isset($invite['CoPetition']['enrollee_org_identity_id'])) { - $orgId = $invite['CoPetition']['enrollee_org_identity_id']; - } elseif(empty($invite['CoPetition'])) { - // Try to find the org identity associated with this invite - - $args = array(); - $args['conditions']['CoOrgIdentityLink.co_person_id'] = $invite['CoPerson']['co_person_id']; - $args['conditions']['EmailAddress.mail'] = $invite['CoInvite']['mail']; - $args['joins'][0]['table'] = 'cm_email_addresses'; - $args['joins'][0]['alias'] = 'EmailAddress'; - $args['joins'][0]['type'] = 'INNER'; - $args['joins'][0]['conditions'][0] = 'CoOrgIdentityLink.org_identity_id=EmailAddress.org_identity_id'; - $args['contain'] = false; - - // This *should* generate one result... - $link = $this->CoPerson->CoOrgIdentityLink->find('first', $args); - - if(!empty($link['CoOrgIdentityLink']['org_identity_id'])) { - $orgId = $link['CoOrgIdentityLink']['org_identity_id']; - } - } - - if($orgId) { - try { - $this->CoPerson->EmailAddress->verify($orgId, null, $invite['CoInvite']['mail'], $invite['CoPetition']['enrollee_co_person_id']); - } - catch(Exception $e) { - $dbc->rollback(); - throw new RuntimeException($e->getMessage()); - } - } - } - - // Toss the invite - $this->delete($invite['CoInvite']['id']); } else { - if(!empty($invite['CoPetition']['id'])) { - // Before we can delete the invitation, we need to unlink it from the petition - - $this->CoPetition->id = $invite['CoPetition']['id']; - $this->CoPetition->saveField('co_invite_id', null); - } - - $this->delete($invite['CoInvite']['id']); // Record a history record that the invitation expired try { @@ -209,6 +155,17 @@ public function processReply($inviteId, $confirm, $loginIdentifier=null) { throw new OutOfBoundsException(_txt('er.inv.exp')); } + if(!empty($invite['CoPetition']['id'])) { + + // Before we can delete the invitation, we need to unlink it from the petition + + $this->CoPetition->id = $invite['CoPetition']['id']; + $this->CoPetition->saveField('co_invite_id', null); + } + + // Toss the invite + $this->delete($invite['CoInvite']['id']); + // Create a history record if($verifyEmail) { diff --git a/app/Model/CoPetition.php b/app/Model/CoPetition.php index 80af75219..d40a06716 100644 --- a/app/Model/CoPetition.php +++ b/app/Model/CoPetition.php @@ -2264,6 +2264,62 @@ public function sendApproverNotification($id, $actorCoPersonId) { return true; } + /** + * Checks for associated email address to find an 'unverified' one + * + * @since COmanage Registry vTODO + * @param Integer CO Petition ID + * @throws InvalidArgumentException + * @return EmailAddress address to be verified, if there are any available + * null if no associated unverified addresses are available + * + * Associated in this context means: directly associated with the Enrollee. + * We check the EmailAddress belonging to the Enrollee OI and COPerson. + * We do not check for linked addresses in any of the OrgIdentityLink records + * at this point + */ + public function needConfirmation($id) { + + $args = array(); + $args['conditions']['CoPetition.id'] = $id; + $args['contain']['EnrolleeCoPerson']= array('EmailAddress'); + $args['contain']['EnrolleeOrgIdentity'] = array('EmailAddress', 'PrimaryName'); + + $pt = $this->find('first', $args); + + if(empty($pt)) { + throw new InvalidArgumentException(_txt('er.notfound', array(_txt('ct.co_petitions.1'), $id))); + } + else { + // check all email addresses and see if any of those are unverified + if(!empty($pt['EnrolleeOrgIdentity']['EmailAddress'])) { + foreach($pt['EnrolleeOrgIdentity']['EmailAddress'] as $em) { + if(!$em['verified']) { + return $em; + } + } + } + else { + throw new RuntimeException(_txt('er.orgp.nomail', + array(generateCn($pt['EnrolleeOrgIdentity']['PrimaryName']), + $pt['EnrolleeOrgIdentity']['id']))); + } + + // check all CoPerson related email addresses + if(!empty($pt['EnrolleeCoPerson']['EmailAddress'])) { + foreach($pt['EnrolleeCoPerson']['EmailAddress'] as $em) { + if(!$em['verified']) { + return $em; + } + } + } + } + + // return null to indicate we did not find any unverified email addresses + + return null; + } + /** * Send a confirmation (invite) for a Petition. * - postcondition: Invite sent @@ -2275,15 +2331,15 @@ public function sendApproverNotification($id, $actorCoPersonId) { * @return String Address the invitation was sent to */ - public function sendConfirmation($id, $actorCoPersonId) { + public function sendConfirmation($id, $ea, $actorCoPersonId) { // Just let any exceptions fall through - + $args = array(); $args['conditions']['CoPetition.id'] = $id; $args['contain']['EnrolleeCoPerson'][] = 'PrimaryName'; $args['contain']['EnrolleeCoPerson']['CoPersonRole'][] = 'Cou'; $args['contain']['EnrolleeCoPerson']['CoPersonRole']['SponsorCoPerson'][] = 'PrimaryName'; - $args['contain']['EnrolleeOrgIdentity'] = array('EmailAddress', 'PrimaryName'); + $args['contain']['EnrolleeOrgIdentity'] = array('PrimaryName'); $pt = $this->find('first', $args); @@ -2291,26 +2347,6 @@ public function sendConfirmation($id, $actorCoPersonId) { throw new InvalidArgumentException(_txt('er.notfound', array(_txt('ct.co_petitions.1'), $id))); } - if(empty($pt['EnrolleeOrgIdentity']['EmailAddress'])) { - throw new RuntimeException(_txt('er.orgp.nomail', - array(generateCn($pt['EnrolleeOrgIdentity']['PrimaryName']), - $pt['EnrolleeOrgIdentity']['id']))); - } - - $toEmail = null; - - // Which email do we pick? Ultimately we could look at type and/or verified, - // but for now we'll just pick the first one. sendApprovalNotification does similar. - // Note array_shift will muck with $pt, but we don't need it anymore. - - $ea = array_shift($pt['EnrolleeOrgIdentity']['EmailAddress']); - - if(empty($ea['mail'])) { - throw new RuntimeException(_txt('er.orgp.nomail', - array(generateCn($pt['EnrolleeOrgIdentity']['PrimaryName']), - $pt['EnrolleeOrgIdentity']['id']))); - } - $toEmail = $ea['mail']; // Now we need some info from the enrollment flow @@ -2369,7 +2405,7 @@ public function sendConfirmation($id, $actorCoPersonId) { $ef['Co']['name'], $subject, $body, - null, + $ea['id'], $ef['CoEnrollmentFlow']['invitation_validity'], $cc, $bcc, @@ -3217,4 +3253,33 @@ private function validateRelated($primaryModel, $requestData, $validatedData, $e return $ret; } } + + /** + * Ensure an enrollee token exists + * + * @since COmanage Registry vTODO + * @param id CoPetition model id + */ + + public function ensureEnrolleeToken($id) { + $args=array(); + $args['conditions']['CoPetition.id']=$id; + $model = $this->find('first', $args); + + $token=null; + // if model is empty, disregard the request instead of throwing exceptions + if(!empty($model) && !empty($model['CoPetition'])) { + if(empty($model['CoPetition']['enrollee_token'])) { + + $token = Security::generateAuthKey(); + $this->id = $id; + $this->saveField('enrollee_token', $token); + } + else { + $token = $model['CoPetition']['enrollee_token']; + } + } + + return $token; + } }