From 3c4e5d516f03b1a3669ded958074eb33b7bdf667 Mon Sep 17 00:00:00 2001 From: Noah Miller Date: Thu, 12 Oct 2023 00:35:51 -0400 Subject: [PATCH] generate encrypted link for email verification; process submission if verified --- ext/afform/core/CRM/Afform/Page/Verify.php | 20 ++- ext/afform/core/Civi/Afform/Tokens.php | 115 ++++++++---------- .../core/templates/CRM/Afform/Page/Verify.tpl | 3 +- 3 files changed, 74 insertions(+), 64 deletions(-) diff --git a/ext/afform/core/CRM/Afform/Page/Verify.php b/ext/afform/core/CRM/Afform/Page/Verify.php index 0b550afff7..ca8e87bcac 100644 --- a/ext/afform/core/CRM/Afform/Page/Verify.php +++ b/ext/afform/core/CRM/Afform/Page/Verify.php @@ -4,9 +4,25 @@ class CRM_Afform_Page_Verify extends CRM_Core_Page { public function run() { $verified = FALSE; + $this->assign('error_message', ''); - // get the submission id - $sid = CRM_Utils_Request::retrieve('sid', 'Positive', $this, TRUE); + $token = CRM_Utils_Request::retrieve('token', 'String', $this, TRUE, NULL, 'GET'); + + try { + $decodedToken = \Civi::service('crypto.jwt')->decode($token); + $sid = $decodedToken['submissionId'] ?? NULL; + } + catch (\Civi\Crypto\Exception\CryptoException $e) { + if (str_contains($e->getMessage(), 'ExpiredException')) { + $this->assign('error_message', ts('Token expired.')); + } + else { + $this->assign('error_message', ts('An error occurred when processing the token.')); + \Civi::log()->warning( + __CLASS__ . ' cannot process a token due to a crypto exception.', + ['exception' => $e]); + } + } if (!empty($sid)) { // check submission status diff --git a/ext/afform/core/Civi/Afform/Tokens.php b/ext/afform/core/Civi/Afform/Tokens.php index f94da14a1d..9a45fe7230 100644 --- a/ext/afform/core/Civi/Afform/Tokens.php +++ b/ext/afform/core/Civi/Afform/Tokens.php @@ -36,6 +36,7 @@ class Tokens extends AutoService implements EventSubscriberInterface { 'hook_civicrm_alterMailContent' => 'applyCkeditorWorkaround', 'hook_civicrm_tokens' => 'hook_civicrm_tokens', 'hook_civicrm_tokenValues' => 'hook_civicrm_tokenValues', + 'civi.token.eval' => 'evaluateTokens', ]; } @@ -106,70 +107,62 @@ class Tokens extends AutoService implements EventSubscriberInterface { } } catch (CryptoException $ex) { - \Civi::log()->warning('Civi\Afform\LegacyTokens cannot generate tokens due to crypto exception.', ['exception' => $ex]); + \Civi::log()->warning(__CLASS__ . ' cannot generate tokens due to a crypto exception.', + ['exception' => $ex]); } } - ///** - // * Expose tokens for use in UI. - // * - // * @param \Civi\Token\Event\TokenRegisterEvent $e - // */ - //public static function onRegister(\Civi\Token\Event\TokenRegisterEvent $e) { - // $tokenForms = static::getTokenForms(); - // foreach ($tokenForms as $tokenName => $afform) { - // $e->register([ - // 'entity' => 'afform', - // 'field' => $tokenName . 'Url', - // 'label' => E::ts('View Form: %1 (URL)', [1 => $afform['title'] ?? $afform['name']]), - // ]); - // $e->register([ - // 'entity' => 'afform', - // 'field' => $tokenName . 'Link', - // 'label' => E::ts('View Form: %1 (Full Hyperlink)', [1 => $afform['title'] ?? $afform['name']]), - // ]); - // } - //} - - ///** - // * Substitute any tokens of the form `{afform.myFormUrl}` or `{afform.myFormLink}` with actual values. - // * - // * @param \Civi\Token\Event\TokenValueEvent $e - // */ - //public static function onEvaluate(\Civi\Token\Event\TokenValueEvent $e) { - // $activeTokens = $e->getTokenProcessor()->getMessageTokens(); - // if (empty($activeTokens['afform'])) { - // return; - // } - // - // $tokenForms = static::getTokenForms(); - // foreach ($tokenForms as $formName => $afform) { - // if (!array_intersect($activeTokens['afform'], ["{$formName}Url", "{$formName}Link"])) { - // continue; - // } - // - // if (empty($afform['server_route'])) { - // \Civi::log() - // ->warning('Civi\Afform\Tokens: Cannot generate link for {formName} -- missing server_route', [ - // 'formName' => $formName, - // ]); - // continue; - // } - // - // foreach ($e->getRows() as $row) { - // /** @var \Civi\Token\TokenRow $row */ - // try { - // $url = self::createUrl($afform, $row->context['contactId']); - // $row->format('text/plain')->tokens('afform', "{$formName}Url", $url); - // $row->format('text/html')->tokens('afform', "{$formName}Link", - // sprintf('%s', htmlentities($url), htmlentities($afform['title'] ?? $afform['name']))); - // } - // catch (CryptoException $e) { - // \Civi::log()->warning('Civi\Afform\Tokens cannot generate tokens due to crypto exception.', ['exception' => $e]); - // } - // } - // } - //} + public static function evaluateTokens(\Civi\Token\Event\TokenValueEvent $e) { + $messageTokens = $e->getTokenProcessor()->getMessageTokens(); + if (empty($messageTokens['afform'])) { + return; + } + + // If these tokens are being used, there will only be a single "row". + // The relevant context is on the TokenProcessor itself, not the row. + $context = $e->getTokenProcessor()->context; + $sid = $context['validateAfformSubmission']['submissionId'] ?? NULL; + if (empty($sid)) { + return; + } + + /** @var \Civi\Token\TokenRow $row */ + $url = self::generateEmailVerificationUrl($sid); + $link = sprintf( + '%s', htmlentities($url), + htmlentities(ts('verify your email address'))); + + foreach ($e->getRows() as $row) { + $row->format('text/plain')->tokens('afform', 'validateSubmissionUrl', $url); + $row->format('text/html')->tokens('afform', 'validateSubmissionLink', $link); + } + } + + + private static function generateEmailVerificationUrl(int $submissionId): string { + // 10 minutes + $expires = \CRM_Utils_Time::time() + (10 * 60); + + try { + /** @var \Civi\Crypto\CryptoJwt $jwt */ + $jwt = \Civi::service('crypto.jwt'); + + $token = $jwt->encode([ + 'exp' => $expires, + // Note: Scope is not the same as "authx" scope. "Authx" tokens are user-login tokens. This one is a more limited access token. + 'scope' => 'afformVerifyEmail', + 'submissionId' => $submissionId, + ]); + } + catch (CryptoException $exception) { + \Civi::log()->warning( + 'Civi\Afform\LegacyTokens cannot generate tokens due to crypto exception.', + ['exception' => $exception]); + } + + return \CRM_Utils_System::url('civicrm/afform/submission/verify', + ['token' => $token], TRUE, NULL, FALSE); + } /** * Get a list of forms that have token support enabled. diff --git a/ext/afform/core/templates/CRM/Afform/Page/Verify.tpl b/ext/afform/core/templates/CRM/Afform/Page/Verify.tpl index 5f56bfb8e4..885bbbee22 100644 --- a/ext/afform/core/templates/CRM/Afform/Page/Verify.tpl +++ b/ext/afform/core/templates/CRM/Afform/Page/Verify.tpl @@ -1,5 +1,6 @@ {if $verified} - {ts}Thank you. Your submission is verified successfuly.{/ts} + {ts}Thank you. Your email was verified successfully and your submission was processed.{/ts} {else} {ts}Sorry, unable to verify your submission.{/ts} + {$error_message} {/if} -- 2.25.1