Skip to content

Commit

Permalink
Merge remote-tracking branch 'magnus-tuf/validator' into tuf-client
Browse files Browse the repository at this point in the history
  • Loading branch information
fancyFranci committed Jun 10, 2022
2 parents 5b8e736 + 130e62d commit 9b62a03
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 2 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"web-auth/webauthn-lib": "2.1.*",
"composer/ca-bundle": "^1.2",
"dragonmantank/cron-expression": "^3.1",
"symfony/validator": "^5.4",
"enshrined/svg-sanitize": "^0.15.4"
},
"require-dev": {
Expand Down
2 changes: 1 addition & 1 deletion installation/sql/mysql/base.sql
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,7 @@ CREATE TABLE IF NOT EXISTS `#__tuf_metadata` (
`id` int NOT NULL AUTO_INCREMENT,
`extension_id` int DEFAULT 0,
`root_json` text DEFAULT NULL,
`target_json` text DEFAULT NULL,
`targets_json` text DEFAULT NULL,
`snapshot_json` text DEFAULT NULL,
`timestamp_json` text DEFAULT NULL,
`mirrors_json` text DEFAULT NULL,
Expand Down
2 changes: 1 addition & 1 deletion libraries/src/TUF/DatabaseStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function offsetExists(mixed $offset): bool
{
$column = $this->getCleanColumn($offset);

return substr($offset, -5) === '.json' && $this->table->hasField($column) && strlen($this->table->$column);
return substr($offset, -5) === '_json' && $this->table->hasField($column) && strlen($this->table->$column);
}

/**
Expand Down
151 changes: 151 additions & 0 deletions libraries/src/TUF/TufValidation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/

namespace Joomla\CMS\TUF;

use Joomla\CMS\Factory;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\ParameterType;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Tuf\Client\GuzzleFileFetcher;
use Tuf\Client\Updater;
use Tuf\Exception\Attack\FreezeAttackException;
use Tuf\Exception\Attack\RollbackAttackException;
use Tuf\Exception\Attack\SignatureThresholdException;
use Tuf\Exception\MetadataException;
use Tuf\JsonNormalizer;

\defined('JPATH_PLATFORM') or die;

/**
* @since __DEPLOY_VERSION__
*/
class TufValidation
{
/**
* The id of the extension to be updated
*
* @var integer
*/
private int $extensionId;

/**
* The params of the validator
*
* @var mixed
*/
private mixed $params;

/**
* Validating updates with TUF
*
* @param integer $extensionId The ID of the extension to be checked
* @param mixed $params The parameters containing the Base-URI, the Metadata- and Targets-Path and mirrors for
* the update
*/
public function __construct(int $extensionId, mixed $params)
{
$this->extensionId = $extensionId;

$resolver = new OptionsResolver;

try
{
$this->configureTufOptions($resolver);
}
catch (\Exception)
{
}

try
{
$params = $resolver->resolve($params);
}
catch (\Exception $e)
{
if ($e instanceof UndefinedOptionsException || $e instanceof InvalidOptionsException)
{
throw $e;
}
}

$this->params = $params;
}

/**
* Configures default values or pass arguments to params
*
* @param OptionsResolver $resolver The OptionsResolver for the params
* @return void
*/
protected function configureTufOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'url_prefix' => 'https://raw.githubusercontent.com',
'metadata_path' => '/joomla/updates/test/repository/',
'targets_path' => '/targets/',
'mirrors' => [],
]
)
->setAllowedTypes('url_prefix', 'string')
->setAllowedTypes('metadata_path', 'string')
->setAllowedTypes('targets_path', 'string')
->setAllowedTypes('mirrors', 'array');
}

/**
* Checks for updates and writes it into the database if they are valid. Then it gets the targets.json content and
* returns it
*
* @return mixed Returns the targets.json if the validation is successful, otherwise null
*/
public function getValidUpdate(): mixed
{
$db = Factory::getContainer()->get(DatabaseDriver::class);

// $db = Factory::getDbo();

$fileFetcher = GuzzleFileFetcher::createFromUri($this->params['url_prefix'], $this->params['metadata_path'], $this->params['targets_path']);
$updater = new Updater(
$fileFetcher,
$this->params['mirrors'],
new DatabaseStorage($db, $this->extensionId)
);

try
{
// Refresh the data if needed, it will be written inside the DB, then we fetch it afterwards and return it to
// the caller
$updater->refresh();
$query = $db->getQuery(true)
->select('targets_json')
->from($db->quoteName('#__tuf_metadata', 'map'))
->where($db->quoteName('map.id') . ' = :id')
->bind(':id', $this->extensionId, ParameterType::INTEGER);
$db->setQuery($query);

$resultArray = (array) $db->loadObject();

return JsonNormalizer::decode($resultArray['targets_json']);
}
catch (FreezeAttackException | MetadataException | SignatureThresholdException | RollbackAttackException $e)
{
// When the validation fails, for example when one file is written but the others don't, we roll back everything
// and cancel the update
$query = $db->getQuery(true)
->delete('#__tuf_metadata')
->columns(['snapshot_json', 'targets_json', 'timestamp_json']);
$db->setQuery($query);

return null;
}
}
}

0 comments on commit 9b62a03

Please sign in to comment.