Skip to content

Commit 01df96d

Browse files
committed
Adding callback for alternative to built in session management
1 parent b0e14eb commit 01df96d

File tree

2 files changed

+70
-14
lines changed

2 files changed

+70
-14
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ should be changed to:
1919
## New OAuth2 PKCE Authentication effective April 1, 2022
2020
As of March 31, 2022, Constant Contact will no longer be supporting versions of this library before 22.3. You must upgrade you app on the Constant Contact site. A new secret is also suggested. You must also upgrade to the 22.3 version of this library. The only code change needed is to pass the parameter array ($_GET) to acquireAccessToken instead of the single code parameter.
2121

22-
This library now requires PHP Session support for authentication. See [PHP Manual](https://www.php.net/manual/en/session.security.php) and [a good best practices article](https://www.phparch.com/2018/01/php-sessions-in-depth/).
22+
This library now requires PHP Session support for authentication. See [PHP Manual](https://www.php.net/manual/en/session.security.php) and [a good best practices article](https://www.phparch.com/2018/01/php-sessions-in-depth/). You can provide your own session management by specifying a callback with **setSessionCallback**.
2323

2424
## Namespaces
2525
This library normalizes the [Constant Contact API](https://v3.developer.constantcontact.com/api_guide/index.html) to modern PHP class standards. All endpoints are first character capitialized. Underscores are removed and followed by a capital letter. Each end point is a class with methods matching the standard REST methods (ie. put, post, delete, put, etc.). The methods take required and optional parameters matching the name specified in the Constant Contact YAML API. In addition, this library supports all definitions of types in the API. See below.

src/ConstantContact/Client.php

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
namespace PHPFUI\ConstantContact;
44

5+
/**
6+
* The Client class needs to store authentication information between PHP sessions in order to function correctly. The class defaults to normal PHP $_SESSION handling,
7+
* but you can specify a callback via **setSessionCallback** to provide a different persistence model. The callback function signature is:
8+
*
9+
* @param string $key used to store or retrieve $value
10+
* @param ?string $value if null, value should be returned and key deleted, if not null, value should be stored by key.
11+
* @return string $value from store or value passed in on set (ignored)
12+
*/
513
class Client
614
{
715
public string $accessToken = '';
@@ -26,13 +34,14 @@ class Client
2634

2735
private string $next = '';
2836

37+
private $sessionCallback = null;
38+
2939
/**
3040
* Construct a client.
3141
*
32-
* By default, all scopes are enabled. You can remove any, or
33-
* set new ones.
42+
* By default, all scopes are enabled. You can remove any, or set new ones.
3443
*/
35-
public function __construct(private string $clientAPIKey, private string $clientSecret, private string $redirectURI = 'https://localhost/', private bool $PKCE = true)
44+
public function __construct(private string $clientAPIKey, private string $clientSecret, private string $redirectURI, public bool $PKCE = true)
3645
{
3746
// default to all scopes
3847
$this->scopes = \array_flip($this->validScopes);
@@ -41,11 +50,32 @@ public function __construct(private string $clientAPIKey, private string $client
4150
$this->guzzleHandler->push(\Spatie\GuzzleRateLimiterMiddleware\RateLimiterMiddleware::perSecond(4));
4251
}
4352

53+
/**
54+
* To avoid using built in PHP Sessions, set the callback to save values yourself
55+
*
56+
* Callback function signature:
57+
*
58+
* @param string $key used to store or retrieve $value
59+
* @param ?string $value if null, value should be returned and key deleted, if not null, value should be stored by key.
60+
* @return string $value from store or value passed in on set (ignored)
61+
*/
62+
public function setSessionCallback(callable $callback) : self
63+
{
64+
$this->sessionCallback = $callback;
65+
66+
return $this;
67+
}
68+
4469
public function getBody() : string
4570
{
4671
return $this->body;
4772
}
4873

74+
/**
75+
* Get the next result set
76+
*
77+
* @return array of data, empty if no more results
78+
*/
4979
public function next() : array
5080
{
5181
if (! $this->next)
@@ -119,7 +149,7 @@ public function getAuthorizationURL() : string
119149
$scopes = \implode('+', \array_keys($this->scopes));
120150

121151
$state = \bin2hex(\random_bytes(8));
122-
$_SESSION['PHPFUI\ConstantContact\state'] = $state;
152+
$this->session('PHPFUI\ConstantContact\state', $state);
123153
$params = [
124154
'response_type' => 'code',
125155
'client_id' => $this->clientAPIKey,
@@ -134,7 +164,7 @@ public function getAuthorizationURL() : string
134164

135165
// Store generated random state and code challenge based on RFC 7636
136166
// https://datatracker.ietf.org/doc/html/rfc7636#section-6.1
137-
$_SESSION['PHPFUI\ConstantContact\code_verifier'] = $code_verifier;
167+
$this->session('PHPFUI\ConstantContact\code_verifier', $code_verifier);
138168
$params['code_challenge'] = $code_challenge;
139169
$params['code_challenge_method'] = 'S256';
140170
}
@@ -162,8 +192,7 @@ public function acquireAccessToken(array $parameters) : bool
162192
return false;
163193
}
164194

165-
$expectedState = $_SESSION['PHPFUI\ConstantContact\state'];
166-
unset($_SESSION['PHPFUI\ConstantContact\state']);
195+
$expectedState = $this->session('PHPFUI\ConstantContact\state', null);
167196

168197
if (($parameters['state'] ?? 'undefined') != $expectedState)
169198
{
@@ -185,8 +214,7 @@ public function acquireAccessToken(array $parameters) : bool
185214

186215
if ($this->PKCE)
187216
{
188-
$params['code_verifier'] = $_SESSION['PHPFUI\ConstantContact\code_verifier'];
189-
unset($_SESSION['PHPFUI\ConstantContact\code_verifier']);
217+
$params['code_verifier'] = $this->session('PHPFUI\ConstantContact\code_verifier', null);
190218
}
191219
$url = $this->oauth2URL . '?' . \http_build_query($params);
192220
\curl_setopt($ch, CURLOPT_URL, $url);
@@ -227,11 +255,17 @@ public function refreshToken() : bool
227255
return $this->exec($ch);
228256
}
229257

258+
/**
259+
* Issue a patch request. This is not normally called directly, but by the V3 namespace classes.
260+
*/
230261
public function patch(string $url, array $parameters) : array
231262
{
232263
return $this->put($url, $parameters, 'PATCH');
233264
}
234265

266+
/**
267+
* Issue a put request. This is not normally called directly, but by the V3 namespace classes.
268+
*/
235269
public function put(string $url, array $parameters, string $method = 'PUT') : array
236270
{
237271
try
@@ -262,6 +296,9 @@ public function put(string $url, array $parameters, string $method = 'PUT') : ar
262296
return [];
263297
}
264298

299+
/**
300+
* Issue a delete request. This is not normally called directly, but by the V3 namespace classes.
301+
*/
265302
public function delete(string $url) : bool
266303
{
267304
try
@@ -282,6 +319,9 @@ public function delete(string $url) : bool
282319
return false;
283320
}
284321

322+
/**
323+
* Issue a get request. This is not normally called directly, but by the V3 namespace classes.
324+
*/
285325
public function get(string $url, array $parameters) : array
286326
{
287327
try
@@ -311,6 +351,9 @@ public function get(string $url, array $parameters) : array
311351
return [];
312352
}
313353

354+
/**
355+
* Issue a post request. This is not normally called directly, but by the V3 namespace classes.
356+
*/
314357
public function post(string $url, array $parameters) : array
315358
{
316359
try
@@ -347,10 +390,6 @@ private function exec(\CurlHandle $ch) : bool
347390

348391
if (isset($data['error']))
349392
{
350-
// [error_description] => Cannot supply multiple client credentials.
351-
// Use one of the following: credentials in the Authorization header,
352-
// credentials in the post body,
353-
// or a client_assertion in the post body.
354393
$this->lastError = $data['error'] . ': ' . ($data['error_description'] ?? 'Undefined');
355394
}
356395
$this->accessToken = $data['access_token'] ?? '';
@@ -445,4 +484,21 @@ private function base64url_encode(string $data) : string
445484
{
446485
return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
447486
}
487+
488+
private function session(string $key, ?string $value) : string
489+
{
490+
if ($this->sessionCallback)
491+
{
492+
return call_user_func($this->sessionCallback, $key, $value);
493+
}
494+
if (null === $value)
495+
{
496+
$value = $_SESSION[$key];
497+
unset($_SESSION[$key]);
498+
499+
return $value;
500+
}
501+
502+
return $_SESSION[$key] = $value;
503+
}
448504
}

0 commit comments

Comments
 (0)