dev/core#2141 - APIv4 - Add OAuthSysToken.refresh action
authorTim Otten <totten@civicrm.org>
Wed, 28 Oct 2020 08:39:15 +0000 (01:39 -0700)
committerTim Otten <totten@civicrm.org>
Tue, 3 Nov 2020 12:32:48 +0000 (04:32 -0800)
ext/oauth-client/Civi/Api4/Action/OAuthSysToken/Refresh.php [new file with mode: 0644]
ext/oauth-client/Civi/Api4/OAuthSysToken.php

diff --git a/ext/oauth-client/Civi/Api4/Action/OAuthSysToken/Refresh.php b/ext/oauth-client/Civi/Api4/Action/OAuthSysToken/Refresh.php
new file mode 100644 (file)
index 0000000..043a83b
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+namespace Civi\Api4\Action\OAuthSysToken;
+
+use Civi\Api4\Generic\BasicBatchAction;
+
+/**
+ * Class Refresh
+ * @package Civi\Api4\Action\OAuthSysToken
+ *
+ * When preparing to connect to a remote OAuth system, use the "refresh" action
+ * to simultaneously refresh and return the auth token.
+ *
+ * Note that it is possible to refresh a token without having permission to view
+ * or edit the specific secrets involved. The result will adjust according to permissions:
+ *
+ * - If permission-checks are disabled, or if you have permission to manage secrets,
+ *   then this will return the full token record.
+ * - If permission-checks are active and you only have access to "refresh" (but
+ *   not to secrets), it will return a minimalist record to indicate completion.
+ *
+ * @method $this setThreshold(int $limit)
+ * @method int getThreshold()
+ */
+class Refresh extends BasicBatchAction {
+
+  /**
+   * Refresh records if they are within the given threshold for expiration.
+   *
+   * Ex: If your token is approaching expiration in 5 seconds, and if your
+   * threshold is 60 seconds, then the token will refresh. But if your token
+   * still has 5 minutes, then there's no need to refresh.
+   *
+   * A negative threshold will always refresh.
+   *
+   * @var int
+   */
+  protected $threshold = 60;
+
+  private $syncFields = ['access_token', 'refresh_token', 'expires', 'token_type'];
+  private $writeFields = ['access_token', 'refresh_token', 'expires', 'token_type', 'raw'];
+  private $providers = [];
+
+  public function __construct($entityName, $actionName) {
+    parent::__construct($entityName, $actionName, '*');
+  }
+
+  protected function doTask($row) {
+    if ($this->threshold >= 0 && \CRM_Utils_Time::getTimeRaw() < $row['expires'] - $this->threshold) {
+      return $this->filterReturn($row);
+    }
+
+    $provider = $this->getProvider($row['client_id']);
+    $newToken = $provider->getAccessToken('refresh_token', [
+      'refresh_token' => $row['refresh_token'],
+    ]);
+
+    $raw = $newToken->jsonSerialize();
+    $row['raw'] = $raw;
+    foreach ($this->syncFields as $field) {
+      if (isset($raw[$field])) {
+        $row[$field] = $raw[$field];
+      }
+    }
+
+    civicrm_api4($this->getEntityName(), 'update', [
+      // You may have permission to refresh even if you can't inspect/update secrets directly.
+      'checkPermissions' => FALSE,
+      'where' => [['id', '=', $row['id']]],
+      'values' => \CRM_Utils_Array::subset($row, $this->writeFields),
+    ])->single();
+
+    return $this->filterReturn($row);
+  }
+
+  protected function getProvider($clientId) {
+    if (!isset($this->providers[$clientId])) {
+      $client = \Civi\Api4\OAuthClient::get(0)->addWhere('id', '=', $clientId)->execute()->single();
+      $this->providers[$clientId] = \Civi::service('oauth2.league')->createProvider($client);
+    }
+    return $this->providers[$clientId];
+  }
+
+  protected function filterReturn($tokenRecord) {
+    return $this->checkPermissions ? \CRM_OAuth_BAO_OAuthSysToken::redact($tokenRecord) : $tokenRecord;
+  }
+
+}
index f4bc08a8475185e4d8c7c6e3eb5bbd50f5b620a6..8cba88ee10f3b0de64131c5766b2e3b82b4f1ad1 100644 (file)
@@ -10,12 +10,24 @@ namespace Civi\Api4;
  */
 class OAuthSysToken extends Generic\DAOEntity {
 
+  /**
+   * Load and conditionally refresh a stored token.
+   *
+   * @param bool $checkPermissions
+   * @return \Civi\Api4\Action\OAuthSysToken\Refresh
+   */
+  public static function refresh($checkPermissions = TRUE) {
+    $action = new \Civi\Api4\Action\OAuthSysToken\Refresh(static::class, __FUNCTION__);
+    return $action->setCheckPermissions($checkPermissions);
+  }
+
   public static function permissions() {
     return [
       'meta' => ['access CiviCRM'],
       'default' => ['manage OAuth client'],
       'delete' => ['manage OAuth client'],
       'get' => ['manage OAuth client'],
+      'refresh' => ['manage OAuth client'],
       'create' => ['manage OAuth client secrets'],
       'update' => ['manage OAuth client secrets'],
       // In theory, there might be cases to 'create' or 'update' an OAuthSysToken