Skip to content

Commit 6eeeeb6

Browse files
authored
Merge pull request #129 from Icinga/ipl-orm-support
Make use of ipl/orm
2 parents a8fd68f + 23c1c99 commit 6eeeeb6

34 files changed

+1434
-692
lines changed

application/clicommands/CheckCommand.php

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66

77
use Icinga\Application\Logger;
88
use Icinga\Module\X509\Command;
9-
use Icinga\Module\X509\DbTool;
10-
use Icinga\Module\X509\Job;
11-
use ipl\Sql\Select;
9+
use Icinga\Module\X509\Model\X509Certificate;
10+
use Icinga\Module\X509\Model\X509CertificateChain;
11+
use Icinga\Module\X509\Model\X509Target;
12+
use ipl\Sql\Expression;
13+
use ipl\Stdlib\Filter;
1214

1315
class CheckCommand extends Command
1416
{
@@ -66,42 +68,55 @@ public function hostAction()
6668
exit(3);
6769
}
6870

69-
$dbTool = new DbTool($this->getDb());
70-
$targets = (new Select())
71-
->from('x509_target t')
72-
->columns([
73-
't.port',
74-
'cc.valid',
75-
'cc.invalid_reason',
76-
'c.subject',
77-
'self_signed' => 'COALESCE(ci.self_signed, c.self_signed)',
78-
'valid_from' => (new Select())
79-
->from('x509_certificate_chain_link xccl')
80-
->columns('MAX(GREATEST(xc.valid_from, xci.valid_from))')
81-
->join('x509_certificate xc', 'xc.id = xccl.certificate_id')
82-
->join('x509_certificate xci', 'xci.subject_hash = xc.issuer_hash')
83-
->where('xccl.certificate_chain_id = cc.id'),
84-
'valid_to' => (new Select())
85-
->from('x509_certificate_chain_link xccl')
86-
->columns('MIN(LEAST(xc.valid_to, xci.valid_to))')
87-
->join('x509_certificate xc', 'xc.id = xccl.certificate_id')
88-
->join('x509_certificate xci', 'xci.subject_hash = xc.issuer_hash')
89-
->where('xccl.certificate_chain_id = cc.id')
71+
$conn = $this->getDb();
72+
$targets = X509Target::on($conn)->with([
73+
'chain',
74+
'chain.certificate',
75+
'chain.certificate.issuer_certificate'
76+
]);
77+
78+
$targets->getWith()['target.chain.certificate.issuer_certificate']->setJoinType('LEFT');
79+
80+
$targets->columns([
81+
'port',
82+
'chain.valid',
83+
'chain.invalid_reason',
84+
'subject' => 'chain.certificate.subject',
85+
'self_signed' => new Expression('COALESCE(%s, %s)', [
86+
'chain.certificate.issuer_certificate.self_signed',
87+
'chain.certificate.self_signed'
88+
])
89+
]);
90+
91+
// Sub queries for (valid_from, valid_to) columns
92+
$validFrom = $targets->createSubQuery(new X509Certificate(), 'chain.certificate');
93+
$validFrom
94+
->columns([new Expression('MAX(GREATEST(%s, %s))', ['valid_from', 'issuer_certificate.valid_from'])])
95+
->getSelectBase()
96+
->resetWhere()
97+
->where(new Expression('sub_certificate_link.certificate_chain_id = target_chain.id'));
98+
99+
$validTo = clone $validFrom;
100+
$validTo->columns([new Expression('MIN(LEAST(%s, %s))', ['valid_to', 'issuer_certificate.valid_to'])]);
101+
102+
list($validFromSelect, $_) = $validFrom->dump();
103+
list($validToSelect, $_) = $validTo->dump();
104+
$targets
105+
->withColumns([
106+
'valid_from' => new Expression($validFromSelect),
107+
'valid_to' => new Expression($validToSelect)
90108
])
91-
->join('x509_certificate_chain cc', 'cc.id = t.latest_certificate_chain_id')
92-
->join('x509_certificate_chain_link ccl', 'ccl.certificate_chain_id = cc.id')
93-
->join('x509_certificate c', 'c.id = ccl.certificate_id')
94-
->joinLeft('x509_certificate ci', 'ci.subject_hash = c.issuer_hash')
95-
->where(['ccl.order = ?' => 0]);
109+
->getSelectBase()
110+
->where(new Expression('target_chain_link.order = 0'));
96111

97112
if ($ip !== null) {
98-
$targets->where(['t.ip = ?' => $dbTool->marshalBinary(Job::binary($ip))]);
113+
$targets->filter(Filter::equal('ip', $ip));
99114
}
100115
if ($hostname !== null) {
101-
$targets->where(['t.hostname = ?' => $hostname]);
116+
$targets->filter(Filter::equal('hostname', $hostname));
102117
}
103118
if ($this->params->has('port')) {
104-
$targets->where(['t.port = ?' => $this->params->get('port')]);
119+
$targets->filter(Filter::equal('port', $this->params->get('port')));
105120
}
106121

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

114129
$state = 3;
115-
foreach ($this->getDb()->select($targets) as $target) {
116-
if ($target['valid'] === 'n' && ($target['self_signed'] === 'n' || ! $allowSelfSigned)) {
117-
$invalidMessage = $target['subject'] . ': ' . $target['invalid_reason'];
130+
foreach ($targets as $target) {
131+
if (! $target->chain->valid && (! $target['self_signed'] || ! $allowSelfSigned)) {
132+
$invalidMessage = $target['subject'] . ': ' . $target->chain->invalid_reason;
118133
$output[$invalidMessage] = $invalidMessage;
119134
$state = 2;
120135
}

application/clicommands/ImportCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function indexAction()
4141
foreach ($bundle as $data) {
4242
$cert = openssl_x509_read($data);
4343

44-
$id = CertificateUtils::findOrInsertCert($db, $cert);
44+
list($id, $_) = CertificateUtils::findOrInsertCert($db, $cert);
4545

4646
$db->update(
4747
'x509_certificate',

application/controllers/CertificateController.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,34 @@
77
use Icinga\Exception\ConfigurationError;
88
use Icinga\Module\X509\CertificateDetails;
99
use Icinga\Module\X509\Controller;
10-
use ipl\Sql;
10+
use Icinga\Module\X509\Model\X509Certificate;
11+
use ipl\Stdlib\Filter;
1112

1213
class CertificateController extends Controller
1314
{
1415
public function indexAction()
1516
{
17+
$this->addTitleTab($this->translate('X.509 Certificate'));
18+
$this->getTabs()->disableLegacyExtensions();
19+
1620
$certId = $this->params->getRequired('cert');
1721

1822
try {
1923
$conn = $this->getDb();
2024
} catch (ConfigurationError $_) {
2125
$this->render('missing-resource', null, true);
26+
2227
return;
2328
}
2429

25-
$cert = $conn->select(
26-
(new Sql\Select())
27-
->from('x509_certificate')
28-
->columns('*')
29-
->where(['id = ?' => $certId])
30-
)->fetch();
30+
$cert = X509Certificate::on($conn)
31+
->filter(Filter::equal('id', $certId))
32+
->first();
3133

32-
if ($cert === false) {
34+
if (! $cert) {
3335
$this->httpNotFound($this->translate('Certificate not found.'));
3436
}
3537

36-
$this->setTitle($this->translate('X.509 Certificate'));
37-
3838
$this->view->certificateDetails = (new CertificateDetails())
3939
->setCert($cert);
4040
}

application/controllers/CertificatesController.php

Lines changed: 79 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -4,126 +4,118 @@
44

55
namespace Icinga\Module\X509\Controllers;
66

7-
use Icinga\Data\Filter\FilterExpression;
87
use Icinga\Exception\ConfigurationError;
98
use Icinga\Module\X509\CertificatesTable;
109
use Icinga\Module\X509\Controller;
11-
use Icinga\Module\X509\FilterAdapter;
12-
use Icinga\Module\X509\SortAdapter;
13-
use Icinga\Module\X509\SqlFilter;
14-
use ipl\Web\Control\PaginationControl;
15-
use ipl\Sql;
16-
use ipl\Web\Url;
10+
use Icinga\Module\X509\Model\X509Certificate;
11+
use Icinga\Module\X509\Web\Control\SearchBar\ObjectSuggestions;
12+
use ipl\Orm\Query;
13+
use ipl\Web\Control\LimitControl;
14+
use ipl\Web\Control\SortControl;
1715

1816
class CertificatesController extends Controller
1917
{
2018
public function indexAction()
2119
{
22-
$this
23-
->initTabs()
24-
->setTitle($this->translate('Certificates'));
20+
$this->addTitleTab($this->translate('Certificates'));
21+
$this->getTabs()->enableDataExports();
2522

2623
try {
2724
$conn = $this->getDb();
2825
} catch (ConfigurationError $_) {
2926
$this->render('missing-resource', null, true);
27+
3028
return;
3129
}
3230

33-
$select = (new Sql\Select())
34-
->from('x509_certificate c')
35-
->columns([
36-
'c.id', 'c.subject', 'c.issuer', 'c.version', 'c.self_signed', 'c.ca', 'c.trusted',
37-
'c.pubkey_algo', 'c.pubkey_bits', 'c.signature_algo', 'c.signature_hash_algo',
38-
'c.valid_from', 'c.valid_to',
39-
]);
40-
41-
$this->view->paginator = new PaginationControl(new Sql\Cursor($conn, $select), Url::fromRequest());
42-
$this->view->paginator->apply();
43-
44-
$sortAndFilterColumns = [
45-
'subject' => $this->translate('Certificate'),
46-
'issuer' => $this->translate('Issuer'),
47-
'version' => $this->translate('Version'),
48-
'self_signed' => $this->translate('Is Self-Signed'),
49-
'ca' => $this->translate('Is Certificate Authority'),
50-
'trusted' => $this->translate('Is Trusted'),
51-
'pubkey_algo' => $this->translate('Public Key Algorithm'),
52-
'pubkey_bits' => $this->translate('Public Key Strength'),
53-
'signature_algo' => $this->translate('Signature Algorithm'),
31+
$certificates = X509Certificate::on($conn);
32+
33+
$sortColumns = [
34+
'subject' => $this->translate('Certificate'),
35+
'issuer' => $this->translate('Issuer'),
36+
'version' => $this->translate('Version'),
37+
'self_signed' => $this->translate('Is Self-Signed'),
38+
'ca' => $this->translate('Is Certificate Authority'),
39+
'trusted' => $this->translate('Is Trusted'),
40+
'pubkey_algo' => $this->translate('Public Key Algorithm'),
41+
'pubkey_bits' => $this->translate('Public Key Strength'),
42+
'signature_algo' => $this->translate('Signature Algorithm'),
5443
'signature_hash_algo' => $this->translate('Signature Hash Algorithm'),
55-
'valid_from' => $this->translate('Valid From'),
56-
'valid_to' => $this->translate('Valid To'),
57-
'duration' => $this->translate('Duration'),
58-
'expires' => $this->translate('Expiration')
44+
'valid_from' => $this->translate('Valid From'),
45+
'valid_to' => $this->translate('Valid To'),
46+
'duration' => $this->translate('Duration'),
47+
'expires' => $this->translate('Expiration')
5948
];
6049

61-
$this->setupSortControl(
62-
$sortAndFilterColumns,
63-
new SortAdapter($select, function ($field) {
64-
if ($field === 'duration') {
65-
return '(valid_to - valid_from)';
66-
} elseif ($field === 'expires') {
67-
return 'CASE WHEN UNIX_TIMESTAMP() > valid_to'
68-
. ' THEN 0 ELSE (valid_to - UNIX_TIMESTAMP()) / 86400 END';
69-
}
70-
})
71-
);
50+
$limitControl = $this->createLimitControl();
51+
$paginator = $this->createPaginationControl($certificates);
52+
$sortControl = $this->createSortControl($certificates, $sortColumns);
7253

73-
$this->setupLimitControl();
54+
$searchBar = $this->createSearchBar($certificates, [
55+
$limitControl->getLimitParam(),
56+
$sortControl->getSortParam()
57+
]);
7458

75-
$filterAdapter = new FilterAdapter();
76-
$this->setupFilterControl(
77-
$filterAdapter,
78-
$sortAndFilterColumns,
79-
['subject', 'issuer'],
80-
['format']
81-
);
59+
if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) {
60+
if ($searchBar->hasBeenSubmitted()) {
61+
$filter = $this->getFilter();
62+
} else {
63+
$this->addControl($searchBar);
64+
$this->sendMultipartUpdate();
8265

83-
(new SqlFilter($conn))->apply($select, $filterAdapter->getFilter(), function (FilterExpression $filter) {
84-
switch ($filter->getColumn()) {
85-
case 'issuer_hash':
86-
$value = $filter->getExpression();
87-
88-
if (is_array($value)) {
89-
$value = array_map('hex2bin', $value);
90-
} else {
91-
$value = hex2bin($value);
92-
}
93-
94-
return $filter->setExpression($value);
95-
case 'duration':
96-
return $filter->setColumn('(valid_to - valid_from)');
97-
case 'expires':
98-
return $filter->setColumn(
99-
'CASE WHEN UNIX_TIMESTAMP() > valid_to THEN 0 ELSE (valid_to - UNIX_TIMESTAMP()) / 86400 END'
100-
);
101-
case 'valid_from':
102-
case 'valid_to':
103-
$expr = $filter->getExpression();
104-
if (! is_numeric($expr)) {
105-
return $filter->setExpression(strtotime($expr));
106-
}
107-
108-
// expression doesn't need changing
109-
default:
110-
return false;
66+
return;
11167
}
112-
});
68+
} else {
69+
$filter = $searchBar->getFilter();
70+
}
11371

114-
$this->handleFormatRequest($conn, $select, function (\PDOStatement $stmt) {
115-
foreach ($stmt as $cert) {
72+
$certificates->peekAhead($this->view->compact);
73+
74+
$certificates->filter($filter);
75+
76+
$this->addControl($paginator);
77+
$this->addControl($sortControl);
78+
$this->addControl($limitControl);
79+
$this->addControl($searchBar);
80+
81+
$this->handleFormatRequest($certificates, function (Query $certificates) {
82+
/** @var X509Certificate $cert */
83+
foreach ($certificates as $cert) {
11684
$cert['valid_from'] = (new \DateTime())
11785
->setTimestamp($cert['valid_from'])
11886
->format('l F jS, Y H:i:s e');
11987
$cert['valid_to'] = (new \DateTime())
12088
->setTimestamp($cert['valid_to'])
12189
->format('l F jS, Y H:i:s e');
12290

123-
yield $cert;
91+
yield array_intersect_key(iterator_to_array($cert), array_flip($cert->getExportableColumns()));
12492
}
12593
});
12694

127-
$this->view->certificatesTable = (new CertificatesTable())->setData($conn->select($select));
95+
$this->addContent((new CertificatesTable())->setData($certificates));
96+
97+
if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
98+
$this->sendMultipartUpdate(); // Updates the browser search bar
99+
}
100+
}
101+
102+
public function completeAction()
103+
{
104+
$this->getDocument()->add(
105+
(new ObjectSuggestions())
106+
->setModel(X509Certificate::class)
107+
->forRequest($this->getServerRequest())
108+
);
109+
}
110+
111+
public function searchEditorAction()
112+
{
113+
$editor = $this->createSearchEditor(X509Certificate::on($this->getDb()), [
114+
LimitControl::DEFAULT_LIMIT_PARAM,
115+
SortControl::DEFAULT_SORT_PARAM
116+
]);
117+
118+
$this->getDocument()->add($editor);
119+
$this->setTitle(t('Adjust Filter'));
128120
}
129121
}

0 commit comments

Comments
 (0)