dbxapp 4.1.3
CMS, Shop, Workflows und modulare Geschäftsanwendungen
Loading...
Searching...
No Matches
dbxUpdateService_test.php
Go to the documentation of this file.
1<?php
2
3declare(strict_types=1);
4
5require_once dirname(__DIR__) . '/include/dbxUpdateService.class.php';
6
8
9function update_test_assert(bool $condition, string $message): void
10{
11 if (!$condition) {
12 throw new RuntimeException($message);
13 }
14}
15
16function update_test_write(string $file, string $content): void
17{
18 $directory = dirname($file);
19 if (!is_dir($directory) && !mkdir($directory, 0775, true) && !is_dir($directory)) {
20 throw new RuntimeException('Testverzeichnis konnte nicht erstellt werden.');
21 }
22 if (file_put_contents($file, $content) === false) {
23 throw new RuntimeException('Testdatei konnte nicht geschrieben werden.');
24 }
25}
26
27function update_test_remove_tree(string $directory): void
28{
29 if (!is_dir($directory)) {
30 return;
31 }
32 $iterator = new RecursiveIteratorIterator(
33 new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS),
34 RecursiveIteratorIterator::CHILD_FIRST
35 );
36 foreach ($iterator as $item) {
37 if ($item->isDir() && !$item->isLink()) {
38 rmdir($item->getPathname());
39 } else {
40 unlink($item->getPathname());
41 }
42 }
43 rmdir($directory);
44}
45
46function update_test_inventory(string $version, array $contents): string
47{
48 $files = array();
49 foreach ($contents as $path => $content) {
50 $files[$path] = hash('sha256', $content);
51 }
52 $files['.dbx-release-files.json'] = null;
53 ksort($files);
54 return json_encode(array(
55 'schema' => 1,
56 'product' => 'dbxapp',
57 'version' => $version,
58 'files' => $files,
59 ), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL;
60}
61
63 string $zipFile,
64 string $version,
65 array $contents,
66 string $extraPath = ''
67): void {
68 $zip = new ZipArchive();
69 if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
70 throw new RuntimeException('Test-ZIP konnte nicht erzeugt werden.');
71 }
72 foreach ($contents as $path => $content) {
73 $zip->addFromString($path, $content);
74 }
75 $zip->addFromString(
76 '.dbx-release-files.json',
78 );
79 if ($extraPath !== '') {
80 $zip->addFromString($extraPath, 'unsafe');
81 }
82 $zip->close();
83}
84
86 string $work,
87 string $zipFile,
88 array $manifest,
89 array $package
90): string {
91 $staging = $work . DIRECTORY_SEPARATOR . 'staging'
92 . DIRECTORY_SEPARATOR . (string)$manifest['version'];
93 if (!is_dir($staging)) {
94 mkdir($staging, 0775, true);
95 }
96 $zip = new ZipArchive();
97 $zip->open($zipFile);
98 $zip->extractTo($staging);
99 $zip->close();
101 $work . DIRECTORY_SEPARATOR . 'staged.json',
102 json_encode(array(
103 'schema' => 1,
104 'staged_at' => gmdate('c'),
105 'zip_file' => $zipFile,
106 'staging_directory' => $staging,
107 'manifest' => $manifest,
108 'files' => $package['files'],
109 ), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
110 );
111 return $staging;
112}
113
114$root = sys_get_temp_dir() . DIRECTORY_SEPARATOR
115 . 'dbx-update-service-' . bin2hex(random_bytes(6));
116
117try {
118 $oldContents = array(
119 'VERSION' => "4.0.1\n",
120 'index.php' => "<?php echo 'old';\n",
121 'dbx/include/dbxApi.php' => "<?php // old api\n",
122 'obsolete.php' => "<?php // obsolete\n",
123 );
124 foreach ($oldContents as $path => $content) {
126 $root . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $path),
128 );
129 }
131 $root . DIRECTORY_SEPARATOR . '.dbx-release-files.json',
132 update_test_inventory('4.0.1', $oldContents)
133 );
134 update_test_write($root . DIRECTORY_SEPARATOR . '.env', "LOCAL=1\n");
136 $root . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR . 'keep.txt',
137 'runtime'
138 );
140 $root . DIRECTORY_SEPARATOR . 'dbx' . DIRECTORY_SEPARATOR . 'modules'
141 . DIRECTORY_SEPARATOR . 'demo' . DIRECTORY_SEPARATOR . 'cfg'
142 . DIRECTORY_SEPARATOR . 'config.local.php',
143 "<?php // local\n"
144 );
145
147 'VERSION' => "4.0.2\n",
148 'index.php' => "<?php echo 'new';\n",
149 'dbx/include/dbxApi.php' => "<?php // new api\n",
150 'new.php' => "<?php // new file\n",
151 );
152 $work = $root . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR . 'update';
153 $zipFile = $work . DIRECTORY_SEPARATOR . 'downloads'
154 . DIRECTORY_SEPARATOR . 'dbxapp-4.0.2.zip';
155 if (!is_dir(dirname($zipFile))) {
156 mkdir(dirname($zipFile), 0775, true);
157 }
159
160 $manifest = array(
161 'schema' => 1,
162 'product' => 'dbxapp',
163 'channel' => 'stable',
164 'version' => '4.0.2',
165 'release_url' => 'https://github.com/ecb-dbxApp/dbxapp/releases/tag/v4.0.2',
166 'zip_url' => 'https://github.com/ecb-dbxApp/dbxapp/releases/download/v4.0.2/dbxapp-4.0.2.zip',
167 'sha256' => hash_file('sha256', $zipFile),
168 'requires' => array(
169 'php' => '>=8.2',
170 'extensions' => array('json', 'pdo', 'zip'),
171 ),
172 );
173
174 $service = new dbxUpdateService($root);
175 $package = $service->inspectPackage($zipFile, $manifest);
177 in_array('new.php', $package['files'], true),
178 'Gültige Paketdatei fehlt in der Prüfung.'
179 );
180
182 $badManifest['zip_url'] = 'https://github.com/other/project/releases/download/v4.0.2/dbxapp-4.0.2.zip';
183 try {
184 $service->validateManifest($badManifest);
185 throw new RuntimeException('Fremde GitHub-Release-URL wurde zugelassen.');
186 } catch (RuntimeException $exception) {
188 str_contains($exception->getMessage(), 'Vertrauensgrenze'),
189 'Falscher Fehler für fremde Release-URL.'
190 );
191 }
192
193 $unsafeZip = $work . DIRECTORY_SEPARATOR . 'downloads'
194 . DIRECTORY_SEPARATOR . 'unsafe.zip';
195 update_test_package($unsafeZip, '4.0.2', $newContents, '../escape.php');
196 try {
197 $service->inspectPackage($unsafeZip, $manifest);
198 throw new RuntimeException('ZIP-Pfad-Traversal wurde zugelassen.');
199 } catch (RuntimeException $exception) {
201 str_contains($exception->getMessage(), 'Pfad-Traversal'),
202 'Falscher Fehler für ZIP-Pfad-Traversal.'
203 );
204 }
205
208 !empty($service->status()['stop_available']),
209 'Vorbereitetes Update kann laut Status nicht gestoppt werden.'
210 );
211 $stopped = $service->cancel();
212 update_test_assert($stopped['version'] === '4.0.2', 'Gestoppte Version ist falsch.');
213 update_test_assert(!is_file($work . '/staged.json'), 'Staging-Status wurde beim Stoppen nicht entfernt.');
214 update_test_assert(!is_dir($staging), 'Staging-Verzeichnis wurde beim Stoppen nicht entfernt.');
215 update_test_assert(!is_file($zipFile), 'Update-ZIP wurde beim Stoppen nicht entfernt.');
217 empty($service->status()['stop_available']),
218 'Status bietet Stoppen nach dem Abbruch weiterhin an.'
219 );
220
221 $outsideGuard = $root . DIRECTORY_SEPARATOR . 'outside-stop-guard.txt';
222 update_test_write($outsideGuard, 'protected');
224 $work . DIRECTORY_SEPARATOR . 'staged.json',
225 json_encode(array(
226 'schema' => 1,
227 'staged_at' => gmdate('c'),
228 'zip_file' => $work . DIRECTORY_SEPARATOR . 'downloads'
229 . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..'
230 . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR
231 . 'outside-stop-guard.txt',
232 'staging_directory' => $work . DIRECTORY_SEPARATOR . 'staging'
233 . DIRECTORY_SEPARATOR . 'missing',
234 'manifest' => $manifest,
235 'files' => array(),
236 ), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
237 );
238 try {
239 $service->cancel();
240 throw new RuntimeException('Manipulierter Stop-Pfad wurde zugelassen.');
241 } catch (RuntimeException $exception) {
243 str_contains($exception->getMessage(), 'Status ist ungültig'),
244 'Falscher Fehler für manipulierten Stop-Pfad.'
245 );
246 }
248 is_file($outsideGuard),
249 'Stop-Pfad hat eine Datei außerhalb des Updatebereichs entfernt.'
250 );
251 unlink($work . DIRECTORY_SEPARATOR . 'staged.json');
252 unlink($outsideGuard);
253
255 $manifest['sha256'] = hash_file('sha256', $zipFile);
257 $installed = $service->install();
258 update_test_assert(trim((string)file_get_contents($root . '/VERSION')) === '4.0.2', 'VERSION wurde nicht aktualisiert.');
259 update_test_assert(!is_file($root . '/obsolete.php'), 'Veraltete Datei wurde nicht entfernt.');
260 update_test_assert(is_file($root . '/new.php'), 'Neue Datei wurde nicht installiert.');
261 update_test_assert((string)file_get_contents($root . '/files/keep.txt') === 'runtime', 'Laufzeitdatei wurde verändert.');
262 update_test_assert((string)file_get_contents($root . '/.env') === "LOCAL=1\n", '.env wurde verändert.');
264 is_dir((string)$installed['backup_directory']),
265 'Dateisicherung wurde nicht erzeugt.'
266 );
267 update_test_assert(!is_file($work . '/staged.json'), 'Staging-Status blieb nach Installation bestehen.');
268 update_test_assert(!is_dir($staging), 'Staging-Verzeichnis blieb nach Installation bestehen.');
269 update_test_assert(!is_file($zipFile), 'Update-ZIP blieb nach Installation bestehen.');
270
271 $service->rollback();
272 update_test_assert(trim((string)file_get_contents($root . '/VERSION')) === '4.0.1', 'Rollback hat VERSION nicht wiederhergestellt.');
273 update_test_assert(is_file($root . '/obsolete.php'), 'Rollback hat veraltete Datei nicht wiederhergestellt.');
274 update_test_assert(!is_file($root . '/new.php'), 'Rollback hat neue Datei nicht entfernt.');
276 (string)file_get_contents($root . '/dbx/modules/demo/cfg/config.local.php') === "<?php // local\n",
277 'Lokale Modulkonfiguration wurde verändert.'
278 );
279
280 echo "dbxUpdateService: Paketprüfung, Stop, Installation und Rollback erfolgreich.\n";
281} finally {
283}
Sicherer dbxApp Release-Updater fuer Dateien und optionale DB-Migrationen.
if(preg_match('/core\.js\? foreach[^"\']*v=(\d+)/', $designTemplate, $assetMatch) !== 1 || !str_contains($shopReference, 'dbxapp-Asset-Version ' . $assetMatch[1])) foreach (array( 'reference\\archive', 'provision_docs_content.php', 'dbxSelfTest') as $needle)(array('Installation'=> $installation, 'Installations-Tutorial'=> $installationTutorial, 'SelfTest'=> $selfTest) as $label=> $html)
catch(RuntimeException $exception) $staging
update_test_package(string $zipFile, string $version, array $contents, string $extraPath='')
catch(RuntimeException $exception) $unsafeZip
update_test_assert(bool $condition, string $message)
update_test_write(string $file, string $content)
update_test_remove_tree(string $directory)
update_test_inventory(string $version, array $contents)
update_test_stage(string $work, string $zipFile, array $manifest, array $package)