generate encrypted link for email verification; process submission if verified
authorNoah Miller <nm@lemnisc.us>
Thu, 12 Oct 2023 04:35:51 +0000 (00:35 -0400)
committerKurund Jalmi <kurundjalmi@thirdsectordesign.org>
Wed, 6 Dec 2023 00:23:33 +0000 (00:23 +0000)
ext/afform/core/CRM/Afform/Page/Verify.php
ext/afform/core/Civi/Afform/Tokens.php
ext/afform/core/templates/CRM/Afform/Page/Verify.tpl

index 0b550afff7bc34cb2c56cd487a5a23bcf45df88f..ca8e87bcacc888ce0860390086fe9507ee142bd4 100644 (file)
@@ -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
index f94da14a1dcd9406a1c3286804012493c06b0ba1..9a45fe72307a3a6aecb934de856e1a44909386fb 100644 (file)
@@ -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('<a href="%s">%s</a>', 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(
+      '<a href="%s">%s</a>', 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.
index 5f56bfb8e4c09b238d00c153261fc077fae01850..885bbbee22c930c7629b2a17daa8e5defdba06ac 100644 (file)
@@ -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}