--- /dev/null
+<?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;
+ }
+
+}
*/
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