Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement TUFAdapter #12

Merged
merged 25 commits into from
Sep 17, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
93fcbfb
wip tuf
nielsnuebel Jun 11, 2022
effde12
Change GuzzleFilteFetcher to HttpFileFetcher
nielsnuebel Jun 11, 2022
3b057a8
add missing semicolon
nielsnuebel Jun 11, 2022
1da619b
initial wip
zero-24 Jun 11, 2022
f6ac9c5
doc block updates
zero-24 Jun 11, 2022
5585d32
wip php minimum & stability
zero-24 Jun 11, 2022
f35a4a9
wip stability validation
zero-24 Jun 11, 2022
59eb40c
property_exists and variable names
zero-24 Jun 11, 2022
ee8d1e9
added tests and tweaks
SniperSister Jun 11, 2022
36010be
Merge pull request #9 from joomla-projects/snipersister/ConstraintsCh…
zero-24 Jun 11, 2022
9965451
wip
nielsnuebel Jun 11, 2022
caecbf3
Merge pull request #8 from joomla-projects/ConstraintsCHecker
nielsnuebel Jun 12, 2022
2442045
change stabilityTags to stability
nielsnuebel Jun 12, 2022
0bed04f
wip tuf
nielsnuebel Jun 12, 2022
b4f00c0
Merge remote-tracking branch 'upstream/tuf' into validator
nielsnuebel Jun 12, 2022
b28fc93
implement HttpFileFetcher
nielsnuebel Jun 12, 2022
ff330d3
Update TufAdapter.php
zero-24 Jun 12, 2022
a4d8b47
remove typehint for paramater to support php 7.2.5
nielsnuebel Jun 12, 2022
dc9bcc7
Merge branch 'nielsnuebel/tuf' of github.com:joomla-projects/joomla-t…
nielsnuebel Jun 12, 2022
258a290
Update ConstraintCheckerTest.php
zero-24 Jun 12, 2022
cf6c1a3
remove whitespace
nielsnuebel Jun 12, 2022
8941c23
Merge branch 'nielsnuebel/tuf' of github.com:joomla-projects/joomla-t…
nielsnuebel Jun 12, 2022
c5c8f42
Update ConstraintChecker.php
zero-24 Jun 12, 2022
5f175af
Apply suggestions from code review
nielsnuebel Jun 12, 2022
b930db6
Merge branch 'tuf' into nielsnuebel/tuf
fancyFranci Sep 17, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ public function update($uids, $minimumStability = Updater::STABILITY_STABLE)
continue;
}

// TODO Load Type based on #__updates_site.type
// if type tuf loadFromTuf
nielsnuebel marked this conversation as resolved.
Show resolved Hide resolved
$update->loadFromXml($instance->detailsurl, $minimumStability);

// Find and use extra_query from update_site if available
Expand Down
10 changes: 5 additions & 5 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 24 additions & 6 deletions libraries/src/TUF/DatabaseStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function __construct(DatabaseDriver $db, int $extensionId)
{
$this->table = new Tuf($db);

$this->table->load($extensionId);
$this->table->load(['extension_id' => $extensionId]);
}

/**
Expand All @@ -51,7 +51,21 @@ 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($column, -5) === '_json' && $this->table->hasField($column) && !is_null($this->table->$column);
}

/**
* Check if an offset/table column exists
*
* @param mixed $offset The offset/database column to check for
*
* @return boolean
*/
public function tableColumnExists(mixed $offset): bool
{
$column = $this->getCleanColumn($offset);

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

/**
Expand Down Expand Up @@ -83,12 +97,14 @@ public function offsetGet($offset): mixed
*/
public function offsetSet($offset, $value): void
{
if (!$this->offsetExists($offset))
if (!$this->tableColumnExists($offset))
{
throw new RoleNotFoundException;
}

$this->table->$offset = $value;
$column = $this->getCleanColumn($offset);

$this->table->$column = $value;

$this->table->store();
}
Expand All @@ -107,9 +123,11 @@ public function offsetUnset($offset): void
throw new RoleNotFoundException;
}

$this->table->$offset = '';
$column = $this->getCleanColumn($offset);

$this->table->$column = null;

$this->table->store();
$this->table->store(true);
}

/**
Expand Down
20 changes: 7 additions & 13 deletions libraries/src/TUF/TufValidation.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Tuf\Client\GuzzleFileFetcher;
use Joomla\CMS\TUF\HttpFileFetcher;
use Tuf\Client\Updater;
use Tuf\Exception\Attack\FreezeAttackException;
use Tuf\Exception\Attack\RollbackAttackException;
Expand All @@ -41,7 +42,7 @@ class TufValidation
*
* @var mixed
*/
private mixed $params;
private $params;

/**
* Validating updates with TUF
Expand Down Expand Up @@ -110,30 +111,23 @@ public function getValidUpdate(): mixed
{
$db = Factory::getContainer()->get(DatabaseDriver::class);

// $db = Factory::getDbo();
$fileFetcher = HttpFileFetcher::createFromUri($this->params['url_prefix'], $this->params['metadata_path'], $this->params['targets_path']);

$storage = new DatabaseStorage($db, $this->extensionId);

$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)
$storage
);

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']);
return $storage['targets.json'];
}
catch (FreezeAttackException | MetadataException | SignatureThresholdException | RollbackAttackException $e)
{
Expand Down
220 changes: 220 additions & 0 deletions libraries/src/Updater/Adapter/TufAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
<?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\Updater\Adapter;

\defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Updater\UpdateAdapter;
use Joomla\CMS\Updater\ConstraintChecker;
use Joomla\CMS\Updater\Updater;
use Joomla\CMS\Version;
use Joomla\CMS\TUF\TufValidation;
use Joomla\Database\ParameterType;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
* TUF Update Adapter Class
*
* @since __DEPLOY_VERSION__
*/
class TufAdapter extends UpdateAdapter
{
private $clientId = [
nielsnuebel marked this conversation as resolved.
Show resolved Hide resolved
'site' => 0,
'administrator' => 1,
'installation' => 2,
'api' => 3,
'cli' => 4
];

/**
* Finds an update.
*
* @param array $options Update options.
*
* @return array|boolean Array containing the array of update sites and array of updates. False on failure
*
* @since __DEPLOY_VERSION__
*/
public function findUpdate($options)
{
$updates = [];
$targets = $this->getUpdateTargets($options);

foreach ($targets as $target)
{
$updateTable = Table::getInstance('update');
$updateTable->set('update_site_id', $options['update_site_id']);

$updateTable->bind($target);

$updates[] = $updateTable;
}

return array('update_sites' => array(), 'updates' => $updates);
}

/**
* Finds targets.
*
* @param array $options Update options.
*
* @return array|boolean Array containing the array of update sites and array of updates. False on failure
*
* @since __DEPLOY_VERSION__
*/
public function getUpdateTargets($options)
{
$versions = array();
$resolver = new OptionsResolver;

try
{
$this->configureUpdateOptions($resolver);
$keys = $resolver->getDefinedOptions();
}
catch (\Exception $e)
{
}

// Get extension_id for TufValidation
$db = $this->parent->getDbo();

$query = $db->getQuery(true)
->select($db->quoteName('extension_id'))
->from($db->quoteName('#__update_sites_extensions'))
->where($db->quoteName('update_site_id') . ' = :id')
->bind(':id', $options['update_site_id'], ParameterType::INTEGER);
$db->setQuery($query);

try
{
$extension_id = $db->loadResult();
}
catch (\RuntimeException $e)
{
// Do nothing
}

$params = [
'url_prefix' => 'https://raw.githubusercontent.com',
'metadata_path' => '/joomla/updates/test/repository/',
'targets_path' => '/targets/',
'mirrors' => []
];

$TufValidation = new TufValidation($extension_id, $params);
$metaData = $TufValidation->getValidUpdate();

$metaData = json_decode($metaData);

if (isset($metaData->signed->targets))
{
foreach ($metaData->signed->targets as $filename => $target)
{
$values = [];

foreach ($keys as $key)
{
if (isset($target->custom->$key))
{
$values[$key] = $target->custom->$key;
}
}

if (isset($values['client']) && is_string($values['client'])
&& key_exists(strtolower($values['client']), $this->clientId))
{
$values['client'] = $this->clientId[strtolower($values['client'])];
}

if (isset($values['infourl']) && isset($values['infourl']->url))
{
$values['infourl'] = $values['infourl']->url;
}

try
{
$values = $resolver->resolve($values);
}
catch (\Exception $e)
{
continue;
}

$versions[$values['version']] = $values;
}

usort($versions, function ($a, $b) {
return version_compare($b['version'], $a['version']);
});

$checker = new ConstraintChecker;

foreach ($versions as $version)
{
if ($checker->check((array) $version))
{
return array($version);
}
}
}

return false;
}

/**
* Configures default values or pass arguments to params
*
* @param OptionsResolver $resolver The OptionsResolver for the params
*
* @return void
*/
nielsnuebel marked this conversation as resolved.
Show resolved Hide resolved
protected function configureUpdateOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'name' => null,
'description' => '',
'element' => '',
'type' => null,
'client' => 1,
'version' => "1",
'data' => '',
'detailsurl' => '',
'infourl' => '',
'downloads' => [],
'targetplatform' => new \StdClass,
'php_minimum' => null,
'supported_databases' => new \StdClass,
'stability' => ''
]
)
->setAllowedTypes('version', 'string')
->setAllowedTypes('name', 'string')
->setAllowedTypes('element', 'string')
->setAllowedTypes('data', 'string')
->setAllowedTypes('description', 'string')
->setAllowedTypes('type', 'string')
->setAllowedTypes('detailsurl', 'string')
->setAllowedTypes('infourl', 'string')
->setAllowedTypes('client', 'int')
->setAllowedTypes('downloads', 'array')
->setAllowedTypes('targetplatform', 'object')
->setAllowedTypes('php_minimum', 'string')
->setAllowedTypes('supported_databases', 'object')
->setAllowedTypes('stability', 'string')
->setRequired(['version']);
}
}
Loading