Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,9 @@ AWS_USE_PATH_STYLE_ENDPOINT=false

VITE_APP_NAME="${APP_NAME}"

# Health interval in hours
HEALTH_CHECK_INTERVAL=24
# Orphan site storage period in days
CLEANUP_SITE_DELAY=7
# Cache time in minutes
TUF_REPO_CACHETIME=5
2 changes: 1 addition & 1 deletion app/Http/Controllers/Api/V1/SiteController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use App\Http\Traits\ApiResponse;
use App\Jobs\CheckSiteHealth;
use App\Models\Site;
use App\RemoteSite\Connection;
use App\Traits\ApiResponse;
use Carbon\Carbon;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace App\Traits;
namespace App\Http\Traits;

use Illuminate\Http\JsonResponse;

Expand Down
12 changes: 12 additions & 0 deletions app/Models/TufMetadata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class TufMetadata extends Model
{
public $timestamps = false;
}
52 changes: 52 additions & 0 deletions app/Providers/TUFServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace App\Providers;

use App\Models\TufMetadata;
use App\TUF\EloquentModelStorage;
use App\TUF\HttpLoader;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\App;
use Illuminate\Support\ServiceProvider;
use Tuf\Client\Updater;
use Tuf\Loader\SizeCheckingLoader;
use Tuf\Metadata\StorageInterface;

class TUFServiceProvider extends ServiceProvider
{
public const REPO_PATH = "https://update.joomla.org/cms/";

/**
* Register services.
*
* @return void
*/
public function register()
{
$this->app->singleton(StorageInterface::class, function ($app) {
// Setup loader
$httpLoader = new HttpLoader(
self::REPO_PATH,
App::make(Client::class)
);

$sizeCheckingLoader = new SizeCheckingLoader($httpLoader);

// Setup storage
$storage = new EloquentModelStorage(TufMetadata::findOrFail(1));

// Create updater
$updater = new Updater(
$sizeCheckingLoader,
$storage
);

// Fetch Updates
$updater->refresh();

$storage->persist();

return $storage;
});
}
}
3 changes: 1 addition & 2 deletions app/RemoteSite/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
namespace App\RemoteSite;

use App\Enum\HttpMethod;
use App\Enum\WebserviceEndpoint;
use App\RemoteSite\Responses\FinalizeUpdate as FinalizeUpdateResponse;
use App\RemoteSite\Responses\HealthCheck as HealthCheckResponse;
use App\RemoteSite\Responses\GetUpdate as GetUpdateResponse;
use App\RemoteSite\Responses\HealthCheck as HealthCheckResponse;
use App\RemoteSite\Responses\PrepareUpdate as PrepareUpdateResponse;
use App\RemoteSite\Responses\ResponseInterface;
use GuzzleHttp\Client;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<?php

namespace App\Enum;
namespace App\RemoteSite;

use App\Enum\HttpMethod;
use App\RemoteSite\Responses\FinalizeUpdate;
use App\RemoteSite\Responses\GetUpdate;
use App\RemoteSite\Responses\HealthCheck;
Expand Down
59 changes: 59 additions & 0 deletions app/TUF/EloquentModelStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace App\TUF;

use App\Models\TufMetadata;
use Tuf\Metadata\StorageBase;

class EloquentModelStorage extends StorageBase
{
public const METADATA_COLUMNS = ['root', 'targets', 'snapshot', 'timestamp', 'mirrors'];

protected TufMetadata $model;

/**
* @var array<string, string>
*/
protected array $container = [];

public function __construct(TufMetadata $model)
{
$this->model = $model;

foreach (self::METADATA_COLUMNS as $column) {
if ($this->model->$column === null) {
continue;
}

$this->write($column, $this->model->$column);
}
}

public function read(string $name): ?string
{
return $this->container[$name] ?? null;
}

public function write(string $name, string $data): void
{
$this->container[$name] = $data;
}

public function delete(string $name): void
{
unset($this->container[$name]);
}

public function persist(): bool
{
foreach (self::METADATA_COLUMNS as $column) {
if (!\array_key_exists($column, $this->container)) {
continue;
}

$this->model->$column = $this->container[$column];
}

return $this->model->save();
}
}
36 changes: 36 additions & 0 deletions app/TUF/HttpLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace App\TUF;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise\Create;
use GuzzleHttp\Promise\PromiseInterface;
use Tuf\Exception\RepoFileNotFound;
use Tuf\Loader\LoaderInterface;

class HttpLoader implements LoaderInterface
{
public function __construct(private readonly string $repositoryPath, private readonly Client $http)
{
}

public function load(string $locator, int $maxBytes): PromiseInterface
{
try {
$response = $this->http->get($this->repositoryPath . $locator);
} catch (RequestException $e) {
if ($e->getResponse()?->getStatusCode() !== 200) {
throw new RepoFileNotFound();
}

throw new HttpLoaderException($e->getMessage(), $e->getCode(), $e);
}

// Rewind to start
$response->getBody()->rewind();

// Return response
return Create::promiseFor($response->getBody());
}
}
9 changes: 9 additions & 0 deletions app/TUF/HttpLoaderException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\TUF;

use Tuf\Exception\TufException;

class HttpLoaderException extends TufException
{
}
46 changes: 46 additions & 0 deletions app/TUF/TufFetcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace App\TUF;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Tuf\Exception\MetadataException;
use Tuf\Metadata\StorageInterface;

class TufFetcher
{
protected StorageInterface $updateStorage;

public function __construct()
{
$this->updateStorage = App::make(StorageInterface::class);
}

public function getReleases(): mixed
{
// Cache response to avoid to make constant calls on the fly
return Cache::remember(
'cms_targets',
(int) config('autoupdates.tuf_repo_cachetime') * 60, // @phpstan-ignore-line
function () {
$targets = $this->updateStorage->getTargets();

// Make sure we have a valid list of targets
if (is_null($targets)) {
throw new MetadataException("Empty targetlist in metadata");
}

// Convert format
return (new Collection($targets->getSigned()['targets']))
->mapWithKeys(function (mixed $target) {
if (!is_array($target) || empty($target['custom']) || !is_array($target['custom'])) {
throw new MetadataException("Empty target custom attribute");
}

return [$target['custom']['version'] => $target['custom']];
});
}
);
}
}
1 change: 1 addition & 0 deletions bootstrap/providers.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
App\Providers\AppServiceProvider::class,
App\Providers\HorizonServiceProvider::class,
App\Providers\HttpclientServiceProvider::class,
App\Providers\TUFServiceProvider::class
];
10 changes: 9 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"laravel/framework": "^11.9",
"laravel/horizon": "^5.29",
"laravel/octane": "^2.5",
"laravel/tinker": "^2.9"
"laravel/tinker": "^2.9",
"php-tuf/php-tuf": "1.0.1"
},
"require-dev": {
"fakerphp/faker": "^1.23",
Expand Down Expand Up @@ -69,6 +70,13 @@
"php-http/discovery": true
}
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/joomla-backports/php-tuf.git",
"no-api": true
}
],
"minimum-stability": "stable",
"prefer-stable": true
}
Loading
Loading