Skip to content

Commit

Permalink
Make use of ipl/orm
Browse files Browse the repository at this point in the history
  • Loading branch information
yhabteab committed Oct 7, 2022
1 parent cfc5f78 commit 098dd13
Show file tree
Hide file tree
Showing 30 changed files with 1,334 additions and 562 deletions.
103 changes: 64 additions & 39 deletions application/clicommands/CheckCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

use Icinga\Application\Logger;
use Icinga\Module\X509\Command;
use Icinga\Module\X509\DbTool;
use Icinga\Module\X509\Job;
use ipl\Sql\Select;
use Icinga\Module\X509\Model\X509Certificate;
use Icinga\Module\X509\Model\X509Target;
use ipl\Sql\Expression;
use ipl\Stdlib\Filter;

class CheckCommand extends Command
{
Expand Down Expand Up @@ -66,42 +68,65 @@ public function hostAction()
exit(3);
}

$dbTool = new DbTool($this->getDb());
$targets = (new Select())
->from('x509_target t')
->columns([
't.port',
'cc.valid',
'cc.invalid_reason',
'c.subject',
'self_signed' => 'COALESCE(ci.self_signed, c.self_signed)',
'valid_from' => (new Select())
->from('x509_certificate_chain_link xccl')
->columns('MAX(GREATEST(xc.valid_from, xci.valid_from))')
->join('x509_certificate xc', 'xc.id = xccl.certificate_id')
->join('x509_certificate xci', 'xci.subject_hash = xc.issuer_hash')
->where('xccl.certificate_chain_id = cc.id'),
'valid_to' => (new Select())
->from('x509_certificate_chain_link xccl')
->columns('MIN(LEAST(xc.valid_to, xci.valid_to))')
->join('x509_certificate xc', 'xc.id = xccl.certificate_id')
->join('x509_certificate xci', 'xci.subject_hash = xc.issuer_hash')
->where('xccl.certificate_chain_id = cc.id')
$conn = $this->getDb();
$targets = X509Target::on($conn)->with([
'chain',
'chain.certificate',
'chain.certificate.parent'
]);

$targets->getWith()['x509_target.chain.certificate.parent']->setJoinType('LEFT');

$targets->columns([
'port',
'chain.valid',
'chain.invalid_reason',
'subject' => 'chain.certificate.subject',
'self_signed' => new Expression('COALESCE(%s, %s)', [
'chain.certificate.parent.self_signed',
'chain.certificate.self_signed'
])
->join('x509_certificate_chain cc', 'cc.id = t.latest_certificate_chain_id')
->join('x509_certificate_chain_link ccl', 'ccl.certificate_chain_id = cc.id')
->join('x509_certificate c', 'c.id = ccl.certificate_id')
->joinLeft('x509_certificate ci', 'ci.subject_hash = c.issuer_hash')
->where(['ccl.order = ?' => 0]);
]);

// Sub queries for (valid_from, valid_to) columns
$validFrom = X509Certificate::on($conn)->with(['chain', 'parent']);
$validFrom->getResolver()->setAliasPrefix('sub_');
$validFrom->columns([
new Expression("MAX(GREATEST(%s, %s))", ['valid_from', 'parent.valid_from'])
]);

$validFrom
->getSelectBase()
->where(new Expression(
'sub_x509_certificate_x509_certificate_chain_link.certificate_chain_id = x509_target_chain.id'
));

$validTo = clone $validFrom;
$validTo->columns([
new Expression('MIN(LEAST(%s, %s))', ['valid_to', 'parent.valid_to'])
]);

list($validFromSelect, $validFromValues) = $validFrom->dump();
list($validToSelect, $validToValues) = $validTo->dump();

$validFromAlias = 'valid_from';
$validToAlias = 'valid_to';

$targets->withColumns([
$validFromAlias => new Expression("$validFromSelect", null, ...$validFromValues),
$validToAlias => new Expression("$validToSelect", null, ...$validToValues)
]);

$targets->getSelectBase()->where(new Expression('x509_target_chain_x509_certificate_chain_link.order = 0'));

if ($ip !== null) {
$targets->where(['t.ip = ?' => $dbTool->marshalBinary(Job::binary($ip))]);
$targets->filter(Filter::equal('ip', Job::binary($ip)));
}
if ($hostname !== null) {
$targets->where(['t.hostname = ?' => $hostname]);
$targets->filter(Filter::equal('hostname', $hostname));
}
if ($this->params->has('port')) {
$targets->where(['t.port = ?' => $this->params->get('port')]);
$targets->filter(Filter::equal('port', (int) $this->params->get('port')));
}

$allowSelfSigned = (bool) $this->params->get('allow-self-signed', false);
Expand All @@ -112,9 +137,9 @@ public function hostAction()
$perfData = [];

$state = 3;
foreach ($this->getDb()->select($targets) as $target) {
if ($target['valid'] === 'no' && ($target['self_signed'] === 'no' || ! $allowSelfSigned)) {
$invalidMessage = $target['subject'] . ': ' . $target['invalid_reason'];
foreach ($targets as $target) {
if (! $target->chain->valid && (! $target['self_signed'] || ! $allowSelfSigned)) {
$invalidMessage = $target['subject'] . ': ' . $target->chain->invalid_reason;
$output[$invalidMessage] = $invalidMessage;
$state = 2;
}
Expand Down Expand Up @@ -181,7 +206,7 @@ public function hostAction()
/**
* Parse the given threshold definition
*
* @param string $threshold
* @param string $threshold
*
* @return array
*/
Expand Down Expand Up @@ -229,10 +254,10 @@ protected function splitThreshold($threshold)
/**
* Convert the given threshold information to a DateTime object
*
* @param \DateTime $from
* @param \DateTime $to
* @param int|\DateInterval $thresholdValue
* @param string $thresholdUnit
* @param \DateTime $from
* @param \DateTime $to
* @param int|\DateInterval $thresholdValue
* @param string $thresholdUnit
*
* @return \DateTime
*/
Expand Down
17 changes: 9 additions & 8 deletions application/controllers/CertificateController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
use Icinga\Exception\ConfigurationError;
use Icinga\Module\X509\CertificateDetails;
use Icinga\Module\X509\Controller;
use Icinga\Module\X509\Model\X509Certificate;
use ipl\Sql;
use ipl\Stdlib\Filter;

class CertificateController extends Controller
{
Expand All @@ -22,18 +24,17 @@ public function indexAction()
return;
}

$cert = $conn->select(
(new Sql\Select())
->from('x509_certificate')
->columns('*')
->where(['id = ?' => $certId])
)->fetch();
$certificates = X509Certificate::on($conn);
$certificates->filter(Filter::equal('id', $certId));

if ($cert === false) {
$cert = $certificates->first();

if (! $cert) {
$this->httpNotFound($this->translate('Certificate not found.'));
}

$this->setTitle($this->translate('X.509 Certificate'));
$this->addTitleTab($this->translate('X.509 Certificate'));
$this->getTabs()->disableLegacyExtensions();

$this->view->certificateDetails = (new CertificateDetails())
->setCert($cert);
Expand Down
166 changes: 80 additions & 86 deletions application/controllers/CertificatesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,21 @@

namespace Icinga\Module\X509\Controllers;

use Icinga\Data\Filter\FilterExpression;
use Icinga\Exception\ConfigurationError;
use Icinga\Module\X509\CertificatesTable;
use Icinga\Module\X509\Controller;
use Icinga\Module\X509\FilterAdapter;
use Icinga\Module\X509\SortAdapter;
use Icinga\Module\X509\SqlFilter;
use ipl\Web\Control\PaginationControl;
use ipl\Sql;
use ipl\Web\Url;
use Icinga\Module\X509\Model\X509Certificate;
use Icinga\Module\X509\Web\Control\SearchBar\ObjectSuggestions;
use ipl\Orm\Query;
use ipl\Web\Control\LimitControl;
use ipl\Web\Control\SortControl;

class CertificatesController extends Controller
{
public function indexAction()
{
$this
->initTabs()
->setTitle($this->translate('Certificates'));
$this->initTabs();
$this->addTitleTab($this->translate('Certificates'));

try {
$conn = $this->getDb();
Expand All @@ -30,100 +27,97 @@ public function indexAction()
return;
}

$select = (new Sql\Select())
->from('x509_certificate c')
$certificates = X509Certificate::on($conn)
->columns([
'c.id', 'c.subject', 'c.issuer', 'c.version', 'c.self_signed', 'c.ca', 'c.trusted',
'c.pubkey_algo', 'c.pubkey_bits', 'c.signature_algo', 'c.signature_hash_algo',
'c.valid_from', 'c.valid_to',
'id', 'subject', 'issuer', 'version', 'self_signed', 'ca', 'trusted',
'pubkey_algo', 'pubkey_bits', 'signature_algo', 'signature_hash_algo',
'valid_from', 'valid_to'
]);

$this->view->paginator = new PaginationControl(new Sql\Cursor($conn, $select), Url::fromRequest());
$this->view->paginator->apply();

$sortAndFilterColumns = [
'subject' => $this->translate('Certificate'),
'issuer' => $this->translate('Issuer'),
'version' => $this->translate('Version'),
'self_signed' => $this->translate('Is Self-Signed'),
'ca' => $this->translate('Is Certificate Authority'),
'trusted' => $this->translate('Is Trusted'),
'pubkey_algo' => $this->translate('Public Key Algorithm'),
'pubkey_bits' => $this->translate('Public Key Strength'),
'signature_algo' => $this->translate('Signature Algorithm'),
'subject' => $this->translate('Certificate'),
'issuer' => $this->translate('Issuer'),
'version' => $this->translate('Version'),
'self_signed' => $this->translate('Is Self-Signed'),
'ca' => $this->translate('Is Certificate Authority'),
'trusted' => $this->translate('Is Trusted'),
'pubkey_algo' => $this->translate('Public Key Algorithm'),
'pubkey_bits' => $this->translate('Public Key Strength'),
'signature_algo' => $this->translate('Signature Algorithm'),
'signature_hash_algo' => $this->translate('Signature Hash Algorithm'),
'valid_from' => $this->translate('Valid From'),
'valid_to' => $this->translate('Valid To'),
'duration' => $this->translate('Duration'),
'expires' => $this->translate('Expiration')
'valid_from' => $this->translate('Valid From'),
'valid_to' => $this->translate('Valid To'),
'duration' => $this->translate('Duration'),
'expires' => $this->translate('Expiration')
];

$this->setupSortControl(
$sortAndFilterColumns,
new SortAdapter($select, function ($field) {
if ($field === 'duration') {
return '(valid_to - valid_from)';
} elseif ($field === 'expires') {
return 'CASE WHEN UNIX_TIMESTAMP() > valid_to'
. ' THEN 0 ELSE (valid_to - UNIX_TIMESTAMP()) / 86400 END';
}
})
);

$this->setupLimitControl();

$filterAdapter = new FilterAdapter();
$this->setupFilterControl(
$filterAdapter,
$sortAndFilterColumns,
['subject', 'issuer'],
['format']
);

(new SqlFilter($conn))->apply($select, $filterAdapter->getFilter(), function (FilterExpression $filter) {
switch ($filter->getColumn()) {
case 'issuer_hash':
$value = $filter->getExpression();

if (is_array($value)) {
$value = array_map('hex2bin', $value);
} else {
$value = hex2bin($value);
}

return $filter->setExpression($value);
case 'duration':
return $filter->setColumn('(valid_to - valid_from)');
case 'expires':
return $filter->setColumn(
'CASE WHEN UNIX_TIMESTAMP() > valid_to THEN 0 ELSE (valid_to - UNIX_TIMESTAMP()) / 86400 END'
);
case 'valid_from':
case 'valid_to':
$expr = $filter->getExpression();
if (! is_numeric($expr)) {
return $filter->setExpression(strtotime($expr));
}

// expression doesn't need changing
default:
return false;
$limitControl = $this->createLimitControl();
$paginator = $this->createPaginationControl($certificates);
$sortControl = $this->createSortControl($certificates, $sortAndFilterColumns);

$searchBar = $this->createSearchBar($certificates, [
$limitControl->getLimitParam(),
$sortControl->getSortParam()
]);

if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) {
if ($searchBar->hasBeenSubmitted()) {
$filter = $this->getFilter();
} else {
$this->addControl($searchBar);
$this->sendMultipartUpdate();
return;
}
});
} else {
$filter = $searchBar->getFilter();
}

$certificates->peekAhead($this->view->compact);

$this->handleFormatRequest($conn, $select, function (\PDOStatement $stmt) {
foreach ($stmt as $cert) {
$certificates->filter($filter);

$this->addControl($paginator);
$this->addControl($sortControl);
$this->addControl($limitControl);
$this->addControl($searchBar);

$this->handleFormatRequest($certificates, function (Query $certificates) {
/** @var X509Certificate $cert */
foreach ($certificates as $cert) {
$cert['valid_from'] = (new \DateTime())
->setTimestamp($cert['valid_from'])
->format('l F jS, Y H:i:s e');
$cert['valid_to'] = (new \DateTime())
->setTimestamp($cert['valid_to'])
->format('l F jS, Y H:i:s e');

yield $cert;
yield iterator_to_array($cert->getIterator());
}
});

$this->view->certificatesTable = (new CertificatesTable())->setData($conn->select($select));
$this->addContent((new CertificatesTable())->setData($certificates));

if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
$this->sendMultipartUpdate(); // Updates the browser search bar
}
}

public function completeAction()
{
$suggestions = new ObjectSuggestions();
$suggestions->setModel(X509Certificate::class);
$suggestions->forRequest($this->getServerRequest());
$this->getDocument()->add($suggestions);
}

public function searchEditorAction()
{
$editor = $this->createSearchEditor(X509Certificate::on($this->getDb()), [
LimitControl::DEFAULT_LIMIT_PARAM,
SortControl::DEFAULT_SORT_PARAM
]);

$this->getDocument()->add($editor);
$this->setTitle(t('Adjust Filter'));
}
}
Loading

0 comments on commit 098dd13

Please sign in to comment.