Skip to content

Commit

Permalink
feat: added verification to update process (#2492)
Browse files Browse the repository at this point in the history
  • Loading branch information
thorsten committed Sep 29, 2023
1 parent b023b8f commit 9a36e18
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 44 deletions.
1 change: 1 addition & 0 deletions phpmyfaq/.htaccess
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ RewriteRule user/request-removal index.php?action=request-remo
RewriteRule user/logout index.php?action=logout [L,QSA]

# Administration API
RewriteRule admin/api/health-check admin/api/updates.php
RewriteRule admin/api/versions admin/api/updates.php
RewriteRule admin/api/update-check admin/api/updates.php
RewriteRule admin/api/download-package admin/api/updates.php
Expand Down
6 changes: 3 additions & 3 deletions phpmyfaq/admin/api/configuration-list.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,9 @@ function renderInputForm(mixed $key, string $type): void
(ReleaseType::DEVELOPMENT->value === $faqConfig->get($key)) ? 'selected' : ''
);
printf(
'<option value="%s" %s>Release</option>',
ReleaseType::RELEASE->value,
(ReleaseType::RELEASE->value === $faqConfig->get($key)) ? 'selected' : ''
'<option value="%s" %s>Stable</option>',
ReleaseType::STABLE->value,
(ReleaseType::STABLE->value === $faqConfig->get($key)) ? 'selected' : ''
);
printf(
'<option value="%s" %s>Nightly</option>',
Expand Down
12 changes: 7 additions & 5 deletions phpmyfaq/admin/assets/src/configuration/upgrade.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const handleCheckForUpdates = () => {
if (downloadButton) {
downloadButton.addEventListener('click', (event) => {
event.preventDefault();
fetch(window.location.pathname + 'api/download-package/nightly', {
fetch(window.location.pathname + 'api/download-package/3.2.1', {
method: 'POST',
headers: {
Accept: 'application/json, text/plain, */*',
Expand All @@ -80,14 +80,16 @@ export const handleCheckForUpdates = () => {
const result = document.getElementById('result-download-nightly');
if (result) {
if (response.version === 'current') {
result.replaceWith(addElement('p', { innerText: response.message }));
result.replaceWith(addElement('p', { innerText: response.success }));
} else {
result.replaceWith(addElement('p', { innerText: response.message }));
result.replaceWith(addElement('p', { innerText: response.success }));
}
}
})
.catch((error) => {
console.error(error);
.catch(async (error) => {
const errorMessage = await error.cause.response.json();
const result = document.getElementById('result-download-nightly');
result.replaceWith(addElement('p', { innerText: errorMessage.error }));
});
});
}
Expand Down
10 changes: 9 additions & 1 deletion phpmyfaq/admin/assets/templates/configuration/upgrade.twig
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@
You can check for new versions of phpMyFAQ, re-install or update your installation.
</p>

<!-- Step 1: Check for a new version -->
<!-- Step 1: Check for system health -->
<div class="d-grid gap-2 d-md-flex justify-content-between mb-5">
<button type="button" class="btn btn-sm btn-primary mb-2" id="pmf-button-check-health">
Check for a health
</button>
<output id="result-check-health"></output>
</div>

<!-- Step 2: Check for a new version -->
<div class="d-grid gap-2 d-md-flex justify-content-between mb-5">
<button type="button" class="btn btn-sm btn-primary mb-2" id="pmf-button-check-updates">
Check for a new version
Expand Down
5 changes: 5 additions & 0 deletions phpmyfaq/src/admin-routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@

$routes = new RouteCollection();

$routes->add(
'admin.api.health-check',
new Route('/health-check', ['_controller' => [UpdateController::class, 'healthCheck']])
);

$routes->add(
'admin.api.updates',
new Route('/versions', ['_controller' => [UpdateController::class, 'versions']])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,33 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;

class UpdateController
{
#[Route('admin/api/health-check')]
public function healthCheck(): JsonResponse
{
$response = new JsonResponse();
$configuration = Configuration::getConfigurationInstance();
$upgrade = new Upgrade(new System(), $configuration);

try {
$upgrade->checkFilesystem();
$response->setStatusCode(Response::HTTP_OK);
$response->setData(['success' => 'ok']);
} catch (Exception $exception) {
$response->setStatusCode(Response::HTTP_BAD_REQUEST);
$response->setData(['error' => $exception->getMessage()]);
}

return $response;
}

#[Route('admin/api/versions')]
public function versions(): JsonResponse
{
Expand All @@ -48,9 +70,9 @@ public function versions(): JsonResponse
);
$response->setStatusCode(Response::HTTP_OK);
$response->setContent($versions->getContent());
} catch (TransportExceptionInterface $e) {
} catch (TransportExceptionInterface $exception) {
$response->setStatusCode(Response::HTTP_BAD_REQUEST);
$response->setData($e->getMessage());
$response->setData($exception->getMessage());
}

return $response;
Expand Down Expand Up @@ -97,23 +119,42 @@ public function updateCheck(): JsonResponse
return $response;
}

/**
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
* @throws \JsonException
*/
#[Route('admin/api/download-package')]
public function downloadPackage(Request $request): JsonResponse
{
$response = new JsonResponse();
$configuration = Configuration::getConfigurationInstance();

$versionNumber = Filter::filterVar($request->get('versionNumber'), FILTER_SANITIZE_SPECIAL_CHARS);

$upgrade = new Upgrade(new System(), Configuration::getConfigurationInstance());
$upgrade->setIsNightly(true);
$result = $upgrade->downloadPackage($versionNumber);
$upgrade = new Upgrade(new System(), $configuration);

if ($result !== false) {
$response->setStatusCode(Response::HTTP_OK);
$response->setData(['success' => $result]);
} else {
$pathToPackage = $upgrade->downloadPackage($versionNumber);

if ($pathToPackage === false) {
$response->setStatusCode(Response::HTTP_BAD_GATEWAY);
$response->setData(['error' => 'Could not download package.']);
return $response;
}

if (!$upgrade->isNightly()) {
$result = $upgrade->verifyPackage($pathToPackage, $versionNumber);
if ($result === false) {
$response->setStatusCode(Response::HTTP_BAD_GATEWAY);
$response->setData(['error' => 'Could not verify downloaded package.']);
return $response;
}
}

$response->setStatusCode(Response::HTTP_OK);
$response->setData(['success' => 'Download package successful.']);
return $response;
}
}
2 changes: 1 addition & 1 deletion phpmyfaq/src/phpMyFAQ/Enums/ReleaseType.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

enum ReleaseType: string
{
case RELEASE = 'release';
case STABLE = 'stable';
case DEVELOPMENT = 'development';
case NIGHTLY = 'nightly';
}
2 changes: 1 addition & 1 deletion phpmyfaq/src/phpMyFAQ/Setup/Installer.php
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ public function __construct(protected System $system)
'main.phpMyFAQToken' => bin2hex(random_bytes(16)),
'spam.enableCaptchaCode' => (extension_loaded('gd') ? 'true' : 'false'),
'upgrade.releaseEnvironment' =>
System::isDevelopmentVersion() ? ReleaseType::DEVELOPMENT->value : ReleaseType::RELEASE->value
System::isDevelopmentVersion() ? ReleaseType::DEVELOPMENT->value : ReleaseType::STABLE->value
];

$this->mainConfig = array_merge($this->mainConfig, $dynMainConfig);
Expand Down
48 changes: 29 additions & 19 deletions phpmyfaq/src/phpMyFAQ/Setup/Upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
use JsonException;
use Monolog\Level;
use phpMyFAQ\Configuration;
use phpMyFAQ\Core\Exception;
use phpMyFAQ\Enums\DownloadHostType;
use phpMyFAQ\Enums\ReleaseType;
use phpMyFAQ\Setup;
use phpMyFAQ\System;
use Symfony\Component\HttpClient\HttpClient;
Expand All @@ -35,55 +37,62 @@ class Upgrade extends Setup
public const GITHUB_PATH = 'thorsten/phpMyFAQ/releases/download/development-nightly-%s/';
private const GITHUB_FILENAME = 'phpMyFAQ-nightly-%s.zip';
private const PHPMYFAQ_FILENAME = 'phpMyFAQ-%s.zip';

private $isNightly = false;
private bool $isNightly;

public function __construct(protected System $system, private readonly Configuration $configuration)
{
parent::__construct($this->system);

$this->isNightly = $this->configuration->get('upgrade.releaseEnvironment') === ReleaseType::NIGHTLY->value;
}

/**
* Method to check if the filesystem is ready for the upgrade
*
* @return bool
* @throws Exception
*/
public function checkFilesystem(): bool
{
if (!is_dir(PMF_CONTENT_DIR . '\upgrades')) {
if (!mkdir(PMF_CONTENT_DIR . '\upgrades')) {
return false;
if (!is_dir(PMF_CONTENT_DIR . '/upgrades')) {
if (!mkdir(PMF_CONTENT_DIR . '/upgrades')) {
throw new Exception('The folder ' . PMF_CONTENT_DIR . '/upgrades is missing.');
}
}
if (
is_dir(PMF_CONTENT_DIR . '\user\attachments') &&
is_dir(PMF_CONTENT_DIR . '\user\images') &&
is_dir(PMF_CONTENT_DIR . '\core\data') &&
is_dir(PMF_ROOT_DIR . '\assets\themes')
is_dir(PMF_CONTENT_DIR . '/user/attachments') &&
is_dir(PMF_CONTENT_DIR . '/user/images') &&
is_dir(PMF_CONTENT_DIR . '/core/data') &&
is_dir(PMF_ROOT_DIR . '/assets/themes')
) {
if (
is_file(PMF_CONTENT_DIR . '\core\config\constants.php') &&
is_file(PMF_CONTENT_DIR . '\core\config\database.php')
is_file(PMF_CONTENT_DIR . '/core/config/constants.php') &&
is_file(PMF_CONTENT_DIR . '/core/config/database.php')
) {
if ($this->configuration->isElasticsearchActive()) {
if (!is_file(PMF_CONTENT_DIR . '\core\config\elasticsearch.php')) {
return false;
if (!is_file(PMF_CONTENT_DIR . '/core/config/elasticsearch.php')) {
throw new Exception(
'The file ' . PMF_CONTENT_DIR . '/core/config/elasticsearch.php is missing.'
);
}
}
if ($this->configuration->isLdapActive()) {
if (!is_file(PMF_CONTENT_DIR . '\core\config\ldap.php')) {
return false;
if (!is_file(PMF_CONTENT_DIR . '/core/config/ldap.php')) {
throw new Exception('The file ' . PMF_CONTENT_DIR . '/core/config/ldap.php is missing.');
}
}
if ($this->configuration->isSignInWithMicrosoftActive()) {
if (!is_file(PMF_CONTENT_DIR . '\core\config\azure.php')) {
return false;
if (!is_file(PMF_CONTENT_DIR . '/core/config/azure.php')) {
throw new Exception('The file ' . PMF_CONTENT_DIR . '/core/config/azure.php is missing.');
}
}

return true;
} else {
return false;
throw new Exception(
'The files ' . PMF_CONTENT_DIR . '/core/config/constant.php and ' .
PMF_CONTENT_DIR . '/core/config/database.php are missing.'
);
}
} else {
return false;
Expand Down Expand Up @@ -145,7 +154,8 @@ public function verifyPackage(string $path, string $version): bool

try {
$responseContent = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
if (hash_file('md5', $path) === $responseContent['zip']['md5']) {

if (md5_file($path) === $responseContent['zip']['md5']) {
return true;
} else {
return false;
Expand Down
Empty file.
Empty file.
9 changes: 4 additions & 5 deletions tests/phpMyFAQ/Setup/UpgradeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
use phpMyFAQ\Database\Sqlite3;
use phpMyFAQ\Enums\DownloadHostType;
use phpMyFAQ\System;
use PHPUnit\Framework\MockObject\Exception;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

class UpgradeTest extends TestCase
{
Expand All @@ -30,11 +34,6 @@ public function testDownloadPackage(): void
$this->assertFalse($actual);
}

public function testVerifyPackage(): void
{
$this->markTestSkipped();
}

public function testCreateTemporaryBackup(): void
{
$this->markTestSkipped();
Expand Down

0 comments on commit 9a36e18

Please sign in to comment.