Skip to content

Commit 3fa5ae8

Browse files
committed
Add script to update encrypted keys for objects and config files
1 parent 94bbd04 commit 3fa5ae8

1 file changed

Lines changed: 231 additions & 0 deletions

File tree

update-encrypted-data-v2.php

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
<?php
2+
3+
if (PHP_SAPI !== 'cli') {
4+
exit("Use the console for running this script");
5+
}
6+
7+
include_once '../system/autoload.php';
8+
9+
use Aurora\Api;
10+
use Symfony\Component\Console\Input\InputArgument;
11+
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Output\OutputInterface;
13+
use Symfony\Component\Console\SingleCommandApplication;
14+
use Symfony\Component\Console\Question\ConfirmationQuestion;
15+
use Symfony\Component\Console\Helper\ProgressBar;
16+
use Illuminate\Database\Capsule\Manager as Capsule;
17+
18+
Api::Init();
19+
20+
abstract class Enums
21+
{
22+
public const file = 1;
23+
public const console = 2;
24+
public const both = 3;
25+
}
26+
function logMessage($output, $message, $mode = Enums::both) {
27+
if ($mode === Enums::console || $mode === Enums::both) {
28+
$output->writeln($message);
29+
}
30+
if ($mode === Enums::file || $mode === Enums::both) {
31+
Api::Log($message, \Aurora\System\Enums\LogLevel::Full, 'update-encryption-key-');
32+
}
33+
}
34+
35+
function logObjectResults($output, $data, $title) {
36+
logMessage($output, " $title:");
37+
foreach ($data as $propName => $ids) {
38+
logMessage($output, " Property: $propName");
39+
logMessage($output, " Ids: " . implode(', ', $ids));
40+
}
41+
logMessage($output, "");
42+
}
43+
44+
function updateEncryptedProp($class, $shortClassName, $propNames, $count, $output) {
45+
$progressBar = new ProgressBar($output, $count);
46+
$progressBar->setFormat('verbose');
47+
$progressBar->setBarCharacter('<info>=</info>');
48+
49+
$progressBar->start();
50+
$aObjectResults= [
51+
'missing' => [],
52+
'updated' => [],
53+
'error' => [],
54+
'empty' => [],
55+
];
56+
57+
foreach ($propNames as $propName) {
58+
$aObjectResults['missing'][$propName] = [];
59+
$aObjectResults['updated'][$propName] = [];
60+
$aObjectResults['error'][$propName] = [];
61+
$aObjectResults['empty'][$propName] = [];
62+
}
63+
64+
$class::where('Properties->EncryptionKeyIsUpdatedV2', false)->orWhere('Properties->EncryptionKeyIsUpdatedV2', null)->chunk(10000, function ($items) use ($propNames, $progressBar, &$aObjectResults, $output) {
65+
foreach ($items as $item) {
66+
$bObjectError = false;
67+
foreach ($propNames as $propName) {
68+
if (strpos($propName, '::') !== false) {
69+
$propValue = $item->getExtendedProp($propName);
70+
71+
$decryptedValue = \Aurora\System\Utils::DecryptValue($propValue);
72+
73+
if ($decryptedValue) {
74+
$item->setExtendedProp($propName, \Aurora\System\Utils::EncryptValue($decryptedValue));
75+
//store updated item id
76+
$aObjectResults['updated'][$propName][] = $item->Id;
77+
} else {
78+
//store failed item id
79+
$bObjectError = true;
80+
$aObjectResults['error'][$propName][] = $item->Id;
81+
}
82+
} else {
83+
$rawValue = trim($item->getRawOriginal($propName));
84+
if ($rawValue !== '') {
85+
// Most of model properties are decrypted automatically when they are read.
86+
$propValue = $item->{$propName};
87+
88+
if ($propValue) {
89+
$item->{$propName} = $propValue;
90+
//store updated item id
91+
$aObjectResults['updated'][$propName][] = $item->Id;
92+
} elseif ($propValue === false || $rawValue !== '' && trim($propValue) === '') {
93+
// false means decryption error, but currently auto encrypted fields return empty strings when value cannot be decrypted
94+
$bObjectError = true;
95+
$aObjectResults['error'][$propName][] = $item->Id;
96+
} elseif ($propValue === null) {
97+
$aObjectResults['missing'][$propName][] = $item->Id;
98+
} elseif (trim($propValue) === '') {
99+
$aObjectResults['empty'][$propName][] = $item->Id;
100+
}
101+
} else {
102+
$aObjectResults['empty'][$propName][] = $item->Id;
103+
}
104+
}
105+
}
106+
$item->setExtendedProp('EncryptionKeyIsUpdatedV2', true);
107+
logMessage($output, "EncryptionKeyIsUpdatedV2: $item->Id: " . (!$bObjectError ? "true" : "false"));
108+
if ($item->save()) {
109+
$progressBar->advance();
110+
} else {
111+
logMessage($output, "Object saving error: $item->getName(): $item->Id");
112+
}
113+
}
114+
});
115+
116+
$progressBar->finish();
117+
logMessage($output, "");
118+
logMessage($output, "'$shortClassName' objects updating results:");
119+
120+
logObjectResults($output, $aObjectResults['updated'], "Updated");
121+
logObjectResults($output, $aObjectResults['error'], "Errors");
122+
logObjectResults($output, $aObjectResults['missing'], "Missing");
123+
logObjectResults($output, $aObjectResults['empty'], "Empty");
124+
}
125+
126+
function updateEncryptedConfig($moduleName, $configName, $output) {
127+
if (Api::$oModuleManager->isModuleLoaded($moduleName)) {
128+
129+
logMessage($output, "Processing $moduleName->$configName: ");
130+
131+
$configValue = Api::$oModuleManager->getModuleConfigValue($moduleName, $configName);
132+
if ($configValue) {
133+
$value = \Aurora\System\Utils::DecryptValue($configValue);
134+
135+
if ($value) {
136+
$value = \Aurora\System\Utils::EncryptValue($value);
137+
Api::$oModuleManager->setModuleConfigValue($moduleName, $configName, $value);
138+
Api::$oModuleManager->saveModuleConfigValue($moduleName);
139+
140+
logMessage($output, "Config file updated");
141+
} else {
142+
logMessage($output, "Can't decrypt config value");
143+
}
144+
} else {
145+
logMessage($output, "Config value not found");
146+
}
147+
}
148+
}
149+
150+
function processObject($class, $props, $input, $output, $helper, $force) {
151+
152+
$classParts = explode('\\', $class);
153+
$shortClassName = end($classParts);
154+
155+
logMessage($output, "Processing $class objects");
156+
157+
if (class_exists($class)) {
158+
$classTablename = with(new $class)->getTable();
159+
if (Capsule::schema()->hasTable($classTablename)) {
160+
if ($force) {
161+
$class::where('Properties->EncryptionKeyIsUpdatedV2', true)->update(['Properties->EncryptionKeyIsUpdatedV2' => false]);
162+
}
163+
164+
$allObjectsCount = $class::count();
165+
$objectsCount = $class::where('Properties->EncryptionKeyIsUpdatedV2', false)->orWhere('Properties->EncryptionKeyIsUpdatedV2', null)->count();
166+
167+
logMessage($output, $allObjectsCount . ' object(s) found, ' . $objectsCount . ' of them have not yet been updated');
168+
if ($objectsCount > 0) {
169+
$question = new ConfirmationQuestion('Update encrypted properties for them? [yes]', true);
170+
if ($helper->ask($input, $output, $question)) {
171+
updateEncryptedProp($class, $shortClassName, $props, $objectsCount, $output);
172+
}
173+
} else {
174+
logMessage($output, 'No objects found');
175+
}
176+
} else {
177+
logMessage($output, "$classTablename table not found");
178+
}
179+
} else {
180+
logMessage($output, "$shortClassName class not found");
181+
}
182+
}
183+
184+
(new SingleCommandApplication())
185+
->setName('Update encryption key script V2') // Optional
186+
->setVersion('2.0.0') // Optional
187+
->addArgument('force', InputArgument::OPTIONAL, 'Force reset EncryptionKeyIsUpdatedV2 flag for all objects')
188+
->setCode(function (InputInterface $input, OutputInterface $output) {
189+
$helper = $this->getHelper('question');
190+
$force = $input->getArgument('force');
191+
192+
// update encrypted data for classes
193+
$objects = [
194+
"\Aurora\Modules\Mail\Models\MailAccount" => ['IncomingPassword'],
195+
"\Aurora\Modules\Mail\Models\Fetcher" => ['IncomingPassword'],
196+
"\Aurora\Modules\Mail\Models\Server" => ['SmtpPassword'],
197+
"\Aurora\Modules\StandardAuth\Models\Account" => ['Password'],
198+
"\Aurora\Modules\Core\Models\User" => ['TwoFactorAuth::BackupCodes', 'TwoFactorAuth::Secret', 'IframeAppWebclient::Password']
199+
];
200+
201+
foreach ($objects as $class => $props) {
202+
processObject($class, $props, $input, $output, $helper, $force);
203+
logMessage($output, "");
204+
}
205+
206+
// update encrypted data in configs
207+
$question = new ConfirmationQuestion('Update encrypted data in config files? [no]', false);
208+
if ($helper->ask($input, $output, $question)) {
209+
$settings = [
210+
'CpanelIntegrator' => 'CpanelPassword',
211+
'LdapChangePasswordPlugin' => 'BindPassword',
212+
'MailChangePasswordFastpanelPlugin' => 'FastpanelAdminPass',
213+
'MailChangePasswordHmailserverPlugin' => 'AdminPass',
214+
'MailChangePasswordIredmailPlugin' => 'DbPass',
215+
'MailChangePasswordIspconfigPlugin' => 'DbPass',
216+
'MailChangePasswordIspmanagerPlugin' => 'ISPmanagerPass',
217+
'MailChangePasswordVirtualminPlugin' => 'VirtualminAdminPass',
218+
'MailSignupDirectadmin' => 'AdminPassword',
219+
'MailSignupFastpanel' => 'FastpanelAdminPass',
220+
'MailSignupPlesk' => 'PleskAdminPassword',
221+
'RocketChatWebclient' => 'AdminPassword',
222+
'StandardResetPassword' => 'NotificationPassword',
223+
'TeamContactsLdap' => 'BindPassword',
224+
];
225+
226+
foreach ($settings as $moduleName => $configName) {
227+
updateEncryptedConfig($moduleName, $configName, $output);
228+
}
229+
logMessage($output, "");
230+
}
231+
})->run();

0 commit comments

Comments
 (0)