Skip to content

Commit e5917b4

Browse files
Mike van den Hoekmvdhoek1
authored andcommitted
feat: encrypt cached data
1 parent 9213273 commit e5917b4

5 files changed

Lines changed: 143 additions & 13 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,23 @@ See [here](https://github.com/OpenWebconcept/plugin-prefill-gravity-forms/blob/m
4646
2. Go to the form settings of the form you want to configure.
4747
3. Scroll down and look for the 'iConnect' panel and configure the settings.
4848

49+
### 🔐 Cache Encryption
50+
51+
To enable secure caching of sensitive data, you **must define an encryption key** in your `wp-config.php` file. This key is used to encrypt and decrypt the cached data and should be kept secret at all times.
52+
53+
Add the following line to your `wp-config.php`:
54+
55+
```php
56+
// Prefill Gravity Forms – Cache Encryption Key
57+
define('PG_CACHE_ENCRYPTION_KEY', 'your-unique-32-character-key');
58+
```
59+
60+
Important:
61+
62+
- Use a randomly generated, 32-character key for strong AES-256 encryption.
63+
- Never store this key in the database.
64+
- Keep it secret and secure — anyone with access to this key can decrypt cached data.
65+
4966
## License
5067

5168
The source code is made available under the [EUPL 1.2 license](https://github.com/OpenWebconcept/plugin-prefill-gravity-forms/blob/main/LICENSE.md). Some of the dependencies are licensed differently, with the BSD or MIT license, for example.

src/PrefillGravityForms/Controllers/BaseController.php

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -299,13 +299,17 @@ protected function getCurlHeaders(string $doelBinding = ''): array
299299

300300
protected function handleCurl(array $args, string $transientKey): array
301301
{
302-
/**
303-
* IMPORTANT NOTE: when adjusting this piece of code, please make sure
304-
* that the transient key is unique per request. Otherwise, different requests
305-
* might return the same cached response.
306-
*/
307-
if ($cachedResponse = CacheService::getArrayFromTransient($transientKey)) {
308-
return $cachedResponse;
302+
try {
303+
/**
304+
* IMPORTANT NOTE: when adjusting this piece of code, please make sure
305+
* that the transient key is unique per request. Otherwise, different requests
306+
* might return the same cached response.
307+
*/
308+
if ($cachedResponse = CacheService::getArrayFromTransient($transientKey)) {
309+
return $cachedResponse;
310+
}
311+
} catch (Exception $e) {
312+
$this->logError('Failed to get transient: ' . $e->getMessage(), $e->getCode());
309313
}
310314

311315
$curl = curl_init();
@@ -352,6 +356,14 @@ protected function handleCurl(array $args, string $transientKey): array
352356
}
353357
}
354358

359+
/**
360+
* Extracts the burgerservicenummer (BSN) from the API response.
361+
*/
362+
protected function extractBSN(array $response): string
363+
{
364+
return (string) ($response['burgerservicenummer'] ?? '');
365+
}
366+
355367
/**
356368
* Validates whether the necessary conditions are met before setting the transient.
357369
*
@@ -361,7 +373,7 @@ protected function handleCurl(array $args, string $transientKey): array
361373
*/
362374
protected function handleTransient(array $response, string $transientKey): void
363375
{
364-
$responseBSN = (string) ($response['burgerservicenummer'] ?? '');
376+
$responseBSN = $this->extractBSN($response);
365377

366378
if ('' === $responseBSN) {
367379
throw new Exception('No burgerservicenummer found in the response.', 404);
@@ -374,7 +386,11 @@ protected function handleTransient(array $response, string $transientKey): void
374386
throw new Exception('Transient key mismatch.', 500);
375387
}
376388

377-
CacheService::setTransient($transientKey, $response);
389+
try {
390+
CacheService::setTransient($transientKey, $response);
391+
} catch (Exception $e) {
392+
$this->logError('Failed to set transient: ' . $e->getMessage(), $e->getCode());
393+
}
378394
}
379395

380396
protected function timeoutOptionCURL(): int

src/PrefillGravityForms/Controllers/WeAreFrankController.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ protected function getExpandFields(string $expand): array
101101
return array_filter(explode(',', $expand));
102102
}
103103

104+
/**
105+
* @inheritDoc
106+
*/
107+
protected function extractBSN(array $response): string
108+
{
109+
return (string) ($response['personen'][0]['burgerservicenummer'] ?? '');
110+
}
111+
104112
protected function fetchPersonData(array $preparedData, string $bsn): array
105113
{
106114
$apiResponse = $this->request($preparedData, $bsn);

src/PrefillGravityForms/Services/CacheService.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,28 @@ class CacheService
88
{
99
private static int $defaultExpiration = HOUR_IN_SECONDS;
1010

11+
/**
12+
* @throws Exception
13+
*/
1114
public static function getArrayFromTransient(string $transientKey): array
1215
{
1316
if ('' === trim($transientKey)) {
14-
return [];
17+
throw new Exception('Transient key is empty.', 500);
1518
}
1619

1720
$cachedResponse = get_transient($transientKey);
1821

19-
if (! is_array($cachedResponse) || [] === $cachedResponse) {
22+
if (! is_string($cachedResponse) || '' === $cachedResponse) {
2023
return [];
2124
}
2225

23-
return $cachedResponse;
26+
$decrypted = EncryptionService::decrypt($cachedResponse);
27+
28+
if (! is_array($decrypted)) {
29+
throw new Exception('Decrypted data is not an array.', 500);
30+
}
31+
32+
return $decrypted;
2433
}
2534

2635
/**
@@ -32,7 +41,9 @@ public static function setTransient(string $transientKey, $data, int $expiration
3241
throw new Exception('Transient key is empty.', 500);
3342
}
3443

35-
set_transient($transientKey, $data, $expiration ?: static::$defaultExpiration);
44+
$encrypted = EncryptionService::encrypt($data);
45+
46+
set_transient($transientKey, $encrypted, $expiration ?: static::$defaultExpiration);
3647
}
3748

3849
/**
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace OWC\PrefillGravityForms\Services;
4+
5+
use Exception;
6+
7+
class EncryptionService
8+
{
9+
private static string $cipher = 'aes-256-gcm';
10+
private const TAG_LENGTH = 16;
11+
12+
/**
13+
* Encrypts the given data array into a secure string.
14+
*/
15+
public static function encrypt(array $data): string
16+
{
17+
$iv = random_bytes(openssl_cipher_iv_length(self::$cipher));
18+
$tag = '';
19+
$plaintext = serialize($data);
20+
21+
$ciphertext = openssl_encrypt(
22+
$plaintext,
23+
self::$cipher,
24+
static::getEncryptionKey(),
25+
0,
26+
$iv,
27+
$tag,
28+
'',
29+
self::TAG_LENGTH
30+
);
31+
32+
return base64_encode($iv . $tag . $ciphertext);
33+
}
34+
35+
/**
36+
* Decrypts the given string back into a data array.
37+
*
38+
* @throws Exception
39+
* @return mixed
40+
*/
41+
public static function decrypt(string $encrypted)
42+
{
43+
$raw = base64_decode($encrypted);
44+
45+
$ivLength = openssl_cipher_iv_length(self::$cipher);
46+
$iv = substr($raw, 0, $ivLength);
47+
$tag = substr($raw, $ivLength, self::TAG_LENGTH);
48+
$ciphertext = substr($raw, $ivLength + self::TAG_LENGTH);
49+
50+
$plaintext = openssl_decrypt(
51+
$ciphertext,
52+
self::$cipher,
53+
static::getEncryptionKey(),
54+
0,
55+
$iv,
56+
$tag
57+
);
58+
59+
if (! is_string($plaintext)) {
60+
throw new Exception('Decryption failed. Invalid data or key.', 500);
61+
}
62+
63+
return unserialize($plaintext);
64+
}
65+
66+
/**
67+
* @throws Exception
68+
*/
69+
private static function getEncryptionKey(): string
70+
{
71+
if (! defined('PG_CACHE_ENCRYPTION_KEY') || strlen(PG_CACHE_ENCRYPTION_KEY) < 32) {
72+
throw new Exception('Encryption key is not defined or too short. Please define a constant PG_CACHE_ENCRYPTION_KEY with at least 32 characters in wp-config.php', 500);
73+
}
74+
75+
76+
return PG_CACHE_ENCRYPTION_KEY;
77+
}
78+
}

0 commit comments

Comments
 (0)