Files
opensourcepos/app/Database/Migrations/20220127000000_convertToCI4.php
T
jekkos 22418fddb9 Redesign encryption/throttle key provisioning as read-only runtime
- checkEncryption()/checkThrottleEncryption() are now read-only guards that
  throw when no valid key is provisioned, instead of writing .env at
  request time.
- Add rotateEncryptionKey() and provisionThrottleKey() for explicit,
  idempotent provisioning.
- Add php spark env:provision (app/Commands/EnvProvision.php) so Docker can
  provision keys once at container startup before any request.
- Add app/Libraries/CI3SecretConverter.php shared CI3->CI4 secret converter
  (AES-128-CBC decrypt + CI4 re-encrypt/verify/save) used by both the
  interactive migration and the docker startup path.
- Refactor convertToCI4 migration to use the shared converter.
- Persist .env in a named volume and run spark env:provision on boot; stop
  baking .env into the shipped image.
- Add guard/rotation/throttle + converter tests; clean up orphaned
  msg_pwd_required language keys across all locales.
2026-09-09 19:44:27 +00:00

93 lines
2.6 KiB
PHP

<?php
namespace App\Database\Migrations;
use App\Libraries\CI3SecretConverter;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Forge;
use CodeIgniter\Database\Migration;
use CodeIgniter\HTTP\Exceptions\RedirectException;
use RuntimeException;
class ConvertToCI4 extends Migration
{
/**
* Constructor.
*/
public function __construct(?Forge $forge = null)
{
parent::__construct($forge);
helper('security');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
if (!executeScript(APPPATH . 'Database/Migrations/sqlscripts/3.4.0_CI4Conversion.sql')) {
throw new DatabaseException('Migration script 3.4.0_CI4Conversion.sql failed. Check logs for details.');
}
$existingKey = (string) config('Encryption')->key;
try {
if ($existingKey !== '' && strlen($existingKey) < 64) {
$this->convertCI3EncryptedData($existingKey);
} else {
if ($existingKey === '') {
rotateEncryptionKey(null);
} else {
checkEncryption();
}
}
} finally {
removeBackup();
}
}
/**
* Revert a migration step.
*/
public function down(): void {}
/**
* Decrypts legacy CI3-encrypted secrets with the old key, rotates to a
* fresh CI4 key, re-encrypts under the new key, verifies the round trip,
* and persists the result.
*
* The actual decryption/encryption is delegated to CI3SecretConverter so
* the docker startup command (env:provision) shares the same code path.
*
* @param string $oldKey the CI3-era key currently in .env
* @throws RedirectException
*/
private function convertCI3EncryptedData(string $oldKey): void
{
$converter = new CI3SecretConverter();
$plain = $converter->decryptAll($oldKey);
rotateEncryptionKey($oldKey);
$encrypted = $converter->encryptAll($plain);
// Verify the round trip before committing so we never lose data.
$success = empty(array_diff_assoc($plain, $converter->verifyAll($encrypted)));
if (!$success) {
abortEncryptionConversion();
throw new RedirectException('login'); // TODO: Need to figure out how to pass the error to the Login controller so that it gets displayed.
}
try {
$converter->saveAll($encrypted);
} catch (RuntimeException $e) {
abortEncryptionConversion();
throw $e;
}
}
}