From a6413da67a4579866f0fd0323ddb23a7b6b9654a Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 18 Sep 2026 17:00:53 +0000
Subject: [PATCH] =Cleaning up the Integrations setup a bit, started the Helcim integration work too
---
inc/integrations/OAuth.php | 210 +++++++++++++++++++++++++++++++++++++--------------
1 files changed, 151 insertions(+), 59 deletions(-)
diff --git a/inc/integrations/OAuth.php b/inc/integrations/OAuth.php
index 5f8085f..5750df4 100644
--- a/inc/integrations/OAuth.php
+++ b/inc/integrations/OAuth.php
@@ -10,8 +10,8 @@
trait OAuth
{
- use _Base, Admin;
- protected bool $hasOAuth = true;
+ use _Base, Admin, ServiceMap;
+ protected OAuthURLs $oauth;
/**
* Child classes must set this (e.g. 'https://api.example.com/v1/')
*/
@@ -20,35 +20,122 @@
protected int $oauthTTL = 300;
protected array $oathScopes = [];
+ protected ?RateLimits $oauthLimiter;
- abstract protected function getOAuthRequestHeaders(): array;
- abstract protected function handleOAuthResponse(array|WP_Error $response, string $method, string $endpoint): array;
+ protected function initializeOAuthRateLimiter():void
+ {
+ $key = $this->service_name;
+ if (!is_null($this->userID)) {
+ $key .= '_'.$this->userID;
+ }
+ $key .= '_oauth';
+ $this->oauthLimiter = new RateLimits($key, ['s' => 1, 'm' => 10, 'h' => 100]);
+ }
+ /**
+ * @return array Defaults to the base getRequestHeaders, but can optionally be overridden
+ */
+ protected function getOAuthRequestHeaders(): array
+ {
+ return $this->getRequestHeaders();
+ }
+ public function getOAuthBaseURL():?string
+ {
+ return $this->baseOAuthUrl;
+ }
+
+ /**
+ * Default to the base handleResponse method, but extensions can modify accordingly
+ * @param array|WP_Error $response
+ * @param string $method
+ * @param string $endpoint
+ * @return array
+ */
+ protected function handleOAuthResponse(array|WP_Error $response, string $method, string $endpoint): array
+ {
+ return $this->handleResponse($response, $method, $endpoint);
+ }
abstract protected function refreshAccessToken(OAuthCredentials $credentials): array;
function addOAuthActions():void
{
- $actions = [
- [
- 'name' => 'Connect to Service',
- 'label' => 'Connect',
- 'icon' => 'plugs-connected',
- 'action' => [$this, 'handleOAuthConnect']
- ],
- [
- 'name' => 'Disconnect from Service',
- 'icon' => 'plugs-disconnected',
- 'action' => [$this, 'handleOAuthDisconnect']
- ],
- [
- 'name' => 'Refresh Token',
- 'icon' => 'arrows-clockwise',
- 'action' => [$this, 'handleOAuthRefreshToken']
- ]
- ];
- foreach ($actions as $a) {
- $this->addAction($a['name'], $a['action'], $a['slug']??null, $a['icon']??null, $a['label']??null);
+ if ($this->isSetUp()) {
+ $actions = [
+ [
+ 'name' => 'Connect to Service',
+ 'label' => 'Connect',
+ 'icon' => 'plugs-connected',
+ 'action' => [$this, 'handleOAuthConnect']
+ ],
+ [
+ 'name' => 'Disconnect from Service',
+ 'label' => 'Disconnect',
+ 'icon' => 'plugs-disconnected',
+ 'action' => [$this, 'handleOAuthDisconnect']
+ ],
+// [
+// 'name' => 'Refresh Token',
+// 'icon' => 'arrows-clockwise',
+// 'action' => [$this, 'handleOAuthRefreshToken']
+// ]
+ ];
+ foreach ($actions as $a) {
+ $this->addAction($a['name'], $a['action'], $a['slug']??null, $a['icon']??null, $a['label']??null);
+ }
}
}
+ public function hasOAuthCredentials(?int $userID = null):bool
+ {
+ if ($userID !== $this->userID) {
+ $this->switchUser($userID);
+ }
+ return $this->hasValidOAuth();
+ }
+ public function handleOAuthConnect():array
+ {
+ $url = $this->getOAuthUrl();
+ if ($url) {
+ return [
+ 'success' => true,
+ 'redirect' => $url,
+ ];
+ }
+ return $this->response(false, 'Failed to generate OAuth URL');
+ }
+
+ public function handleOAuthDisconnect():array
+ {
+ $revoked = $this->revokeOAuth();
+ return $this->response($revoked, $revoked ?
+ 'Successfully disconnected from '.$this->title :
+ 'Failed to disconnect from '.$this->title);
+ }
+
+ public function revokeOAuth():bool
+ {
+ if (empty($this->oauth->getRevoke())) {
+ error_log('No revoke url set for '.$this->service_name);
+ return false;
+ }
+ $credentials = $this->loadCredentials();
+ if (empty($credentials['access_token'])) {
+ error_log('Could not send revoke request, no access_token for '.$this->service_name);
+ } else {
+ $response = $this->postOAuthRequest($this->oauth->getRevoke(), [
+ 'body' => [
+ 'token' => $credentials['access_token']
+ ]
+ ]);
+ $empty = new OAuthCredentials([]);
+ foreach ($empty->toArray() as $key => $value) {
+ if (array_key_exists($key, $credentials)) {
+ unset($credentials[$key]);
+ }
+ }
+
+ return Auth::getInstance()->storeCredentials($this->service_name, $credentials, $this->userID);
+ }
+ return false;
+ }
protected function registerOAuthCallbacks():void
{
@@ -105,10 +192,6 @@
return $this->handleOAuthResponse($error, $method, $endpoint);
}
- if (!empty($this->requiredScopes) && !$credentials->hasScopes($this->requiredScopes)) {
- $error = new WP_Error('insufficient_scope', "Missing required scopes for {$this->service_name}");
- return $this->handleOAuthResponse($error, $method, $endpoint);
- }
$url = rtrim($this->baseOAuthUrl, '/') . '/' . ltrim($endpoint, '/');
@@ -215,12 +298,14 @@
}
// Get integration instance
- $integration = JVB()->connect($this->service_name, $user_id);
+ $integration = $this->getService($this->service_name);
- if (!$integration) {
+ if (!$integration || !method_exists($integration, 'switchUser')) {
wp_die('Invalid service: ' . esc_html($this->service_name), 'OAuth Error');
}
+ $integration->switchUser($user_id);
+
// Exchange code for tokens
$result = $integration->handleOAuthCode($code, $state);
@@ -246,20 +331,23 @@
$credentials = $this->loadCredentials();
$hasCredentials = $this->hasValidOAuth();
$returnURL = $this->getReturnURL();
-
- if ($hasCredentials) {
+ if (!$this->isSetUp()) {
+ return '';
+ }
+ if (!$hasCredentials) {
return sprintf(
'<div class="oauth-connect">
<h3>Connect to your %s account</h3>
- <a href="%s" class="btn jvb-oauth-connect" data-service="%s">%s Authorize Connection</a>',
+ <a href="%s" class="btn jvb-oauth-connect" data-service="%s">%s Authorize Connection</a>
+ </div>',
$this->title,
$this->getOAuthURL($returnURL),
esc_attr($this->service_name),
$this->outputIcon($this->icon)
);
- }
- $form = sprintf(
- '<details open>
+ } else {
+ $form = sprintf(
+ '<details open>
<summary>Connected Account
<div class="connection-status connected">
<span class="status-indicator">●</span>
@@ -268,22 +356,23 @@
</summary>
<span class="label">OAuth Redirect URL:</span>
<code>%s</code>',
- $this->getRedirectUri()
- );
- if (!empty($credentials['updated_at'])) {
- $form .= sprintf(
- '<div class="oauth-meta"><small>Token expires: %s</small></div>',
- array_key_exists('expires_at', $credentials) ?
+ $this->getRedirectUri()
+ );
+ if (!empty($credentials['updated_at'])) {
+ $form .= sprintf(
+ '<div class="oauth-meta"><small>Token expires: %s</small></div>',
+ array_key_exists('expires_at', $credentials) ?
human_time_diff($credentials['expires_at']) :
'Never'
- );
+ );
+ }
+ //
+ if (method_exists($this, 'renderOAuthConnectedOptions')) {
+ $form .= $this->renderOAuthConnectedOptions();
+ }
+ $form .= '</details>';
+ return $form;
}
- //
- if (method_exists($this, 'renderOAuthConnectedOptions')) {
- $form .= $this->renderOAuthConnectedOptions();
- }
- $form .= '</details';
- return $form;
}
/**
@@ -296,11 +385,9 @@
public function getOAuthURL(?string $return_url = null):string
{
- if (!$this->hasOAuth) {
- return '';
- }
+
$credentials = $this->loadCredentials();
- if (empty($credentials['authorize'])) {
+ if (empty($this->oauth->getAuthorize())) {
$this->logError('getOAuthUrl', 'OAuth authorize URL not configured');
return '';
}
@@ -308,7 +395,7 @@
'client_id' => $credentials['client_id']??$credentials['application_id']??$credentials['app_id']??'',
'redirect_url' => $this->getRedirectUri(),
'response_type' => 'code',
- 'scope' => implode(' ', $credentials['scopes']??[])
+ 'scope' => implode(' ', $this->oauthScopes??[])
];
$state_key = wp_generate_password(32, false);
$user_id = $this->userID??0;
@@ -338,14 +425,19 @@
$params = $this->addOAuthParams($params);
}
- return $credentials['authorize'] . '?' . http_build_query($params);
+ return $this->oauth->getAuthorize() . '?' . http_build_query($params);
}
protected function getRedirectUri():string
{
- if ($this->hasOAuth) {
- return admin_url('admin-ajax.php?action='.BASE . $this->service_name . '_oauth_callback');
- }
- return rest_url('jvb/v1/oauth/callback?service=' . $this->service_name);
+ return admin_url('admin-ajax.php?action='.BASE . $this->service_name . '_oauth_callback');
+ }
+
+ /****************************************************
+ * UTILITY
+ ****************************************************/
+ protected function getAccessToken():string
+ {
+ return $this->loadCredentials()['access_token']??'';
}
}
--
Gitblit v1.10.0