summaryrefslogtreecommitdiffstats
path: root/application/controllers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:31:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:31:28 +0000
commit067008c5f094ba9606daacbe540f6b929dc124ea (patch)
tree3092ce2cd8bf1ac6db6c97f4c98c7f71a51c6ac8 /application/controllers
parentInitial commit. (diff)
downloadicingaweb2-module-x509-upstream.tar.xz
icingaweb2-module-x509-upstream.zip
Adding upstream version 1:1.3.2.upstream/1%1.3.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--application/controllers/CertificateController.php43
-rw-r--r--application/controllers/CertificatesController.php117
-rw-r--r--application/controllers/ChainController.php77
-rw-r--r--application/controllers/ConfigController.php30
-rw-r--r--application/controllers/DashboardController.php153
-rw-r--r--application/controllers/JobController.php226
-rw-r--r--application/controllers/JobsController.php66
-rw-r--r--application/controllers/SniController.php103
-rw-r--r--application/controllers/UsageController.php141
9 files changed, 956 insertions, 0 deletions
diff --git a/application/controllers/CertificateController.php b/application/controllers/CertificateController.php
new file mode 100644
index 0000000..016b312
--- /dev/null
+++ b/application/controllers/CertificateController.php
@@ -0,0 +1,43 @@
+<?php
+
+// Icinga Web 2 X.509 Module | (c) 2018 Icinga GmbH | GPLv2
+
+namespace Icinga\Module\X509\Controllers;
+
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\X509\CertificateDetails;
+use Icinga\Module\X509\Common\Database;
+use Icinga\Module\X509\Controller;
+use Icinga\Module\X509\Model\X509Certificate;
+use ipl\Stdlib\Filter;
+
+class CertificateController extends Controller
+{
+ public function indexAction()
+ {
+ $this->addTitleTab($this->translate('X.509 Certificate'));
+ $this->getTabs()->disableLegacyExtensions();
+
+ $certId = $this->params->getRequired('cert');
+
+ try {
+ $conn = Database::get();
+ } catch (ConfigurationError $_) {
+ $this->render('missing-resource', null, true);
+
+ return;
+ }
+
+ /** @var ?X509Certificate $cert */
+ $cert = X509Certificate::on($conn)
+ ->filter(Filter::equal('id', $certId))
+ ->first();
+
+ if (! $cert) {
+ $this->httpNotFound($this->translate('Certificate not found.'));
+ }
+
+ $this->view->certificateDetails = (new CertificateDetails())
+ ->setCert($cert);
+ }
+}
diff --git a/application/controllers/CertificatesController.php b/application/controllers/CertificatesController.php
new file mode 100644
index 0000000..37434fa
--- /dev/null
+++ b/application/controllers/CertificatesController.php
@@ -0,0 +1,117 @@
+<?php
+
+// Icinga Web 2 X.509 Module | (c) 2018 Icinga GmbH | GPLv2
+
+namespace Icinga\Module\X509\Controllers;
+
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\X509\CertificatesTable;
+use Icinga\Module\X509\Common\Database;
+use Icinga\Module\X509\Controller;
+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->addTitleTab($this->translate('Certificates'));
+ $this->getTabs()->enableDataExports();
+
+ try {
+ $conn = Database::get();
+ } catch (ConfigurationError $_) {
+ $this->render('missing-resource', null, true);
+
+ return;
+ }
+
+ $certificates = X509Certificate::on($conn);
+
+ $sortColumns = [
+ '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')
+ ];
+
+ $limitControl = $this->createLimitControl();
+ $paginator = $this->createPaginationControl($certificates);
+ $sortControl = $this->createSortControl($certificates, $sortColumns);
+
+ $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);
+
+ $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 = $cert->valid_from->format('l F jS, Y H:i:s e');
+ $cert->valid_to = $cert->valid_to->format('l F jS, Y H:i:s e');
+
+ yield array_intersect_key(iterator_to_array($cert), array_flip($cert->getExportableColumns()));
+ }
+ });
+
+ $this->addContent((new CertificatesTable())->setData($certificates));
+
+ if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
+ $this->sendMultipartUpdate(); // Updates the browser search bar
+ }
+ }
+
+ public function completeAction()
+ {
+ $this->getDocument()->add(
+ (new ObjectSuggestions())
+ ->setModel(X509Certificate::class)
+ ->forRequest($this->getServerRequest())
+ );
+ }
+
+ public function searchEditorAction()
+ {
+ $editor = $this->createSearchEditor(X509Certificate::on(Database::get()), [
+ LimitControl::DEFAULT_LIMIT_PARAM,
+ SortControl::DEFAULT_SORT_PARAM
+ ]);
+
+ $this->getDocument()->add($editor);
+ $this->setTitle(t('Adjust Filter'));
+ }
+}
diff --git a/application/controllers/ChainController.php b/application/controllers/ChainController.php
new file mode 100644
index 0000000..5408526
--- /dev/null
+++ b/application/controllers/ChainController.php
@@ -0,0 +1,77 @@
+<?php
+
+// Icinga Web 2 X.509 Module | (c) 2018 Icinga GmbH | GPLv2
+
+namespace Icinga\Module\X509\Controllers;
+
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\X509\ChainDetails;
+use Icinga\Module\X509\Common\Database;
+use Icinga\Module\X509\Controller;
+use Icinga\Module\X509\Model\X509Certificate;
+use Icinga\Module\X509\Model\X509CertificateChain;
+use ipl\Html\Html;
+use ipl\Html\HtmlDocument;
+use ipl\Stdlib\Filter;
+
+class ChainController extends Controller
+{
+ public function indexAction()
+ {
+ $this->addTitleTab($this->translate('X.509 Certificate Chain'));
+ $this->getTabs()->disableLegacyExtensions();
+
+ $id = $this->params->getRequired('id');
+
+ try {
+ $conn = Database::get();
+ } catch (ConfigurationError $_) {
+ $this->render('missing-resource', null, true);
+ return;
+ }
+
+ /** @var ?X509CertificateChain $chain */
+ $chain = X509CertificateChain::on($conn)
+ ->with(['target'])
+ ->filter(Filter::equal('id', $id))
+ ->first();
+
+ if (! $chain) {
+ $this->httpNotFound($this->translate('Certificate not found.'));
+ }
+
+ $chainInfo = Html::tag('div');
+ $chainInfo->add(Html::tag('dl', [
+ Html::tag('dt', $this->translate('Host')),
+ Html::tag('dd', $chain->target->hostname),
+ Html::tag('dt', $this->translate('IP')),
+ Html::tag('dd', $chain->target->ip),
+ Html::tag('dt', $this->translate('Port')),
+ Html::tag('dd', $chain->target->port)
+ ]));
+
+ $valid = Html::tag('div', ['class' => 'cert-chain']);
+
+ if ($chain['valid']) {
+ $valid->getAttributes()->add('class', '-valid');
+ $valid->add(Html::tag('p', $this->translate('Certificate chain is valid.')));
+ } else {
+ $valid->getAttributes()->add('class', '-invalid');
+ $valid->add(Html::tag('p', sprintf(
+ $this->translate('Certificate chain is invalid: %s.'),
+ $chain['invalid_reason']
+ )));
+ }
+
+ $certs = X509Certificate::on($conn)->with(['chain']);
+ $certs
+ ->filter(Filter::equal('chain.id', $id))
+ ->getSelectBase()
+ ->orderBy('certificate_link.order');
+
+ $this->view->chain = (new HtmlDocument())
+ ->add($chainInfo)
+ ->add($valid)
+ ->add((new ChainDetails())->setData($certs));
+ }
+}
diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php
new file mode 100644
index 0000000..b4300ef
--- /dev/null
+++ b/application/controllers/ConfigController.php
@@ -0,0 +1,30 @@
+<?php
+
+// Icinga Web 2 X.509 Module | (c) 2018 Icinga GmbH | GPLv2
+
+namespace Icinga\Module\X509\Controllers;
+
+use Icinga\Application\Config;
+use Icinga\Module\X509\Forms\Config\BackendConfigForm;
+use Icinga\Web\Controller;
+
+class ConfigController extends Controller
+{
+ public function init()
+ {
+ $this->assertPermission('config/modules');
+
+ parent::init();
+ }
+
+ public function backendAction()
+ {
+ $form = (new BackendConfigForm())
+ ->setIniConfig(Config::module('x509'));
+
+ $form->handleRequest();
+
+ $this->view->tabs = $this->Module()->getConfigTabs()->activate('backend');
+ $this->view->form = $form;
+ }
+}
diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php
new file mode 100644
index 0000000..8b43761
--- /dev/null
+++ b/application/controllers/DashboardController.php
@@ -0,0 +1,153 @@
+<?php
+
+// Icinga Web 2 X.509 Module | (c) 2018 Icinga GmbH | GPLv2
+
+namespace Icinga\Module\X509\Controllers;
+
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\X509\CertificateUtils;
+use Icinga\Module\X509\Common\Database;
+use Icinga\Module\X509\Controller;
+use Icinga\Module\X509\Donut;
+use Icinga\Module\X509\Model\X509Certificate;
+use Icinga\Web\Url;
+use ipl\Html\Html;
+use ipl\Sql\Expression;
+use ipl\Stdlib\Filter;
+
+class DashboardController extends Controller
+{
+ public function indexAction()
+ {
+ $this->addTitleTab($this->translate('Certificate Dashboard'));
+ $this->getTabs()->disableLegacyExtensions();
+
+ try {
+ $db = Database::get();
+ } catch (ConfigurationError $_) {
+ $this->render('missing-resource', null, true);
+ return;
+ }
+
+ $byCa = X509Certificate::on($db)
+ ->columns([
+ 'issuer_certificate.subject',
+ 'cnt' => new Expression('COUNT(*)')
+ ])
+ ->orderBy('cnt', SORT_DESC)
+ ->orderBy('issuer_certificate.subject')
+ ->filter(Filter::equal('issuer_certificate.ca', true))
+ ->limit(5);
+
+ $byCa
+ ->getSelectBase()
+ ->groupBy('certificate_issuer_certificate.id');
+
+ $this->view->byCa = (new Donut())
+ ->setHeading($this->translate('Certificates by CA'), 2)
+ ->setData($byCa)
+ ->setLabelCallback(function ($data) {
+ return Html::tag(
+ 'a',
+ [
+ 'href' => Url::fromPath('x509/certificates', [
+ 'issuer' => $data->issuer_certificate->subject
+ ])->getAbsoluteUrl()
+ ],
+ $data->issuer_certificate->subject
+ );
+ });
+
+ $duration = X509Certificate::on($db)
+ ->columns([
+ 'duration',
+ 'cnt' => new Expression('COUNT(*)')
+ ])
+ ->filter(Filter::equal('ca', false))
+ ->orderBy('cnt', SORT_DESC)
+ ->limit(5);
+
+ $duration
+ ->getSelectBase()
+ ->groupBy('duration');
+
+ $this->view->duration = (new Donut())
+ ->setHeading($this->translate('Certificates by Duration'), 2)
+ ->setData($duration)
+ ->setLabelCallback(function ($data) {
+ return Html::tag(
+ 'a',
+ [
+ 'href' => Url::fromPath(
+ "x509/certificates?duration={$data->duration->getTimestamp()}&ca=n"
+ )->getAbsoluteUrl()
+ ],
+ CertificateUtils::duration($data->duration->getTimestamp())
+ );
+ });
+
+ $keyStrength = X509Certificate::on($db)
+ ->columns([
+ 'pubkey_algo',
+ 'pubkey_bits',
+ 'cnt' => new Expression('COUNT(*)')
+ ])
+ ->orderBy('cnt', SORT_DESC)
+ ->limit(5);
+
+ $keyStrength
+ ->getSelectBase()
+ ->groupBy(['pubkey_algo', 'pubkey_bits']);
+
+ $this->view->keyStrength = (new Donut())
+ ->setHeading($this->translate('Key Strength'), 2)
+ ->setData($keyStrength)
+ ->setLabelCallback(function ($data) {
+ return Html::tag(
+ 'a',
+ [
+ 'href' => Url::fromPath(
+ 'x509/certificates',
+ [
+ 'pubkey_algo' => $data->pubkey_algo,
+ 'pubkey_bits' => $data->pubkey_bits
+ ]
+ )->getAbsoluteUrl()
+ ],
+ "{$data->pubkey_algo} {$data->pubkey_bits} bits"
+ );
+ });
+
+ $sigAlgos = X509Certificate::on($db)
+ ->columns([
+ 'signature_algo',
+ 'signature_hash_algo',
+ 'cnt' => new Expression('COUNT(*)')
+ ])
+ ->orderBy('cnt', SORT_DESC)
+ ->limit(5);
+
+ $sigAlgos
+ ->getSelectBase()
+ ->groupBy(['signature_algo', 'signature_hash_algo']);
+
+ $this->view->sigAlgos = (new Donut())
+ ->setHeading($this->translate('Signature Algorithms'), 2)
+ ->setData($sigAlgos)
+ ->setLabelCallback(function ($data) {
+ return Html::tag(
+ 'a',
+ [
+ 'href' => Url::fromPath(
+ 'x509/certificates',
+ [
+ 'signature_hash_algo' => $data->signature_hash_algo,
+ 'signature_algo' => $data->signature_algo
+ ]
+ )->getAbsoluteUrl()
+ ],
+ "{$data->signature_hash_algo} with {$data->signature_algo}"
+ );
+ });
+ }
+}
diff --git a/application/controllers/JobController.php b/application/controllers/JobController.php
new file mode 100644
index 0000000..7655a74
--- /dev/null
+++ b/application/controllers/JobController.php
@@ -0,0 +1,226 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Controllers;
+
+use Icinga\Module\X509\Common\Database;
+use Icinga\Module\X509\Common\Links;
+use Icinga\Module\X509\Forms\Jobs\JobConfigForm;
+use Icinga\Module\X509\Model\X509Job;
+use Icinga\Module\X509\Model\X509Schedule;
+use Icinga\Module\X509\Forms\Jobs\ScheduleForm;
+use Icinga\Module\X509\Widget\JobDetails;
+use Icinga\Module\X509\Widget\Schedules;
+use Icinga\Util\Json;
+use ipl\Html\Contract\FormSubmitElement;
+use ipl\Html\ValidHtml;
+use ipl\Scheduler\Contract\Frequency;
+use ipl\Stdlib\Filter;
+use ipl\Web\Compat\CompatController;
+use ipl\Web\Url;
+use ipl\Web\Widget\ActionBar;
+use ipl\Web\Widget\ActionLink;
+use ipl\Web\Widget\ButtonLink;
+use stdClass;
+
+class JobController extends CompatController
+{
+ /** @var X509Job */
+ protected $job;
+
+ public function init()
+ {
+ parent::init();
+
+ $this->getTabs()->disableLegacyExtensions();
+
+ /** @var int $jobId */
+ $jobId = $this->params->getRequired('id');
+
+ /** @var X509Job $job */
+ $job = X509Job::on(Database::get())
+ ->filter(Filter::equal('id', $jobId))
+ ->first();
+
+ if ($job === null) {
+ $this->httpNotFound($this->translate('Job not found'));
+ }
+
+ $this->job = $job;
+ }
+
+ public function indexAction(): void
+ {
+ $this->assertPermission('config/x509');
+
+ $this->initTabs();
+ $this->getTabs()->activate('job-activities');
+
+ $jobRuns = $this->job->job_run->with(['job', 'schedule']);
+
+ $limitControl = $this->createLimitControl();
+ $sortControl = $this->createSortControl($jobRuns, [
+ 'schedule.name' => $this->translate('Schedule Name'),
+ 'schedule.author' => $this->translate('Author'),
+ 'total_targets' => $this->translate('Total Targets'),
+ 'finished_targets' => $this->translate('Finished Targets'),
+ 'start_time desc' => $this->translate('Started At'),
+ 'end_time' => $this->translate('Ended At')
+ ]);
+
+ $this->controls->getAttributes()->add('class', 'default-layout');
+ $this->addControl($sortControl);
+ $this->addControl($limitControl);
+ $this->addControl($this->createActionBar());
+
+ $this->addContent(new JobDetails($jobRuns));
+ }
+
+ public function updateAction(): void
+ {
+ $this->assertPermission('config/x509');
+
+ $this->addTitleTab($this->translate('Update Job'));
+
+ $form = (new JobConfigForm($this->job))
+ ->setAction((string) Url::fromRequest())
+ ->populate([
+ 'name' => $this->job->name,
+ 'cidrs' => $this->job->cidrs,
+ 'ports' => $this->job->ports,
+ 'exclude_targets' => $this->job->exclude_targets
+ ])
+ ->on(JobConfigForm::ON_SUCCESS, function (JobConfigForm $form) {
+ /** @var FormSubmitElement $button */
+ $button = $form->getPressedSubmitElement();
+ if ($button->getName() === 'btn_remove') {
+ $this->switchToSingleColumnLayout();
+ } else {
+ $this->closeModalAndRefreshRelatedView(Links::job($this->job));
+ }
+ })
+ ->handleRequest($this->getServerRequest());
+
+ $this->addContent($form);
+ }
+
+ public function schedulesAction(): void
+ {
+ $this->assertPermission('config/x509');
+
+ $this->initTabs();
+ $this->getTabs()->activate('schedules');
+
+ $schedules = $this->job->schedule->with(['job']);
+
+ $sortControl = $this->createSortControl($schedules, [
+ 'name' => $this->translate('Name'),
+ 'author' => $this->translate('Author'),
+ 'ctime' => $this->translate('Date Created'),
+ 'mtime' => $this->translate('Date Modified')
+ ]);
+
+ $this->controls->getAttributes()->add('class', 'default-layout');
+ $this->addControl(
+ (new ButtonLink($this->translate('New Schedule'), Links::scheduleJob($this->job), 'plus'))
+ ->openInModal()
+ );
+ $this->addControl($sortControl);
+
+ $this->addContent(new Schedules($schedules));
+ }
+
+ public function scheduleAction(): void
+ {
+ $this->assertPermission('config/x509');
+
+ $this->addTitleTab($this->translate('Schedule Job'));
+
+ $form = (new ScheduleForm())
+ ->setAction((string) Url::fromRequest())
+ ->setJobId($this->job->id)
+ ->on(JobConfigForm::ON_SUCCESS, function () {
+ $this->redirectNow(Links::schedules($this->job));
+ })
+ ->handleRequest($this->getServerRequest());
+
+ $parts = $form->getPartUpdates();
+ if (! empty($parts)) {
+ $this->sendMultipartUpdate(...$parts);
+ }
+
+ $this->addContent($form);
+ }
+
+ public function updateScheduleAction(): void
+ {
+ $this->assertPermission('config/x509');
+
+ $this->addTitleTab($this->translate('Update Schedule'));
+
+ /** @var int $id */
+ $id = $this->params->getRequired('scheduleId');
+ /** @var X509Schedule $schedule */
+ $schedule = X509Schedule::on(Database::get())
+ ->filter(Filter::equal('id', $id))
+ ->first();
+ if ($schedule === null) {
+ $this->httpNotFound($this->translate('Schedule not found'));
+ }
+
+ /** @var stdClass $config */
+ $config = Json::decode($schedule->config);
+ /** @var Frequency $type */
+ $type = $config->type;
+ $frequency = $type::fromJson($config->frequency);
+
+ $form = (new ScheduleForm($schedule))
+ ->setAction((string) Url::fromRequest())
+ ->populate([
+ 'name' => $schedule->name,
+ 'full_scan' => $config->full_scan ?? 'n',
+ 'rescan' => $config->rescan ?? 'n',
+ 'since_last_scan' => $config->since_last_scan ?? null,
+ 'schedule_element' => $frequency
+ ])
+ ->on(JobConfigForm::ON_SUCCESS, function () {
+ $this->redirectNow('__BACK__');
+ })
+ ->handleRequest($this->getServerRequest());
+
+ $parts = $form->getPartUpdates();
+ if (! empty($parts)) {
+ $this->sendMultipartUpdate(...$parts);
+ }
+
+ $this->addContent($form);
+ }
+
+ protected function createActionBar(): ValidHtml
+ {
+ $actions = new ActionBar();
+ $actions->addHtml(
+ (new ActionLink($this->translate('Modify'), Links::updateJob($this->job), 'edit'))
+ ->openInModal(),
+ (new ActionLink($this->translate('Schedule'), Links::scheduleJob($this->job), 'calendar'))
+ ->openInModal()
+ );
+
+ return $actions;
+ }
+
+ protected function initTabs(): void
+ {
+ $tabs = $this->getTabs();
+ $tabs
+ ->add('job-activities', [
+ 'label' => $this->translate('Job Activities'),
+ 'url' => Links::job($this->job)
+ ])
+ ->add('schedules', [
+ 'label' => $this->translate('Schedules'),
+ 'url' => Links::schedules($this->job)
+ ]);
+ }
+}
diff --git a/application/controllers/JobsController.php b/application/controllers/JobsController.php
new file mode 100644
index 0000000..48deede
--- /dev/null
+++ b/application/controllers/JobsController.php
@@ -0,0 +1,66 @@
+<?php
+
+// Icinga Web 2 X.509 Module | (c) 2018 Icinga GmbH | GPLv2
+
+namespace Icinga\Module\X509\Controllers;
+
+use Icinga\Module\X509\Common\Database;
+use Icinga\Module\X509\Forms\Jobs\JobConfigForm;
+use Icinga\Module\X509\Model\X509Job;
+use Icinga\Module\X509\Widget\Jobs;
+use ipl\Web\Compat\CompatController;
+use ipl\Web\Url;
+use ipl\Web\Widget\ButtonLink;
+
+class JobsController extends CompatController
+{
+ /**
+ * List all jobs
+ */
+ public function indexAction()
+ {
+ $this->addTitleTab($this->translate('Jobs'));
+ $this->getTabs()->add('sni', [
+ 'title' => $this->translate('Configure SNI'),
+ 'label' => $this->translate('SNI'),
+ 'url' => 'x509/sni',
+ 'baseTarget' => '_main'
+ ]);
+
+ $jobs = X509Job::on(Database::get());
+ if ($this->hasPermission('config/x509')) {
+ $this->addControl(
+ (new ButtonLink($this->translate('New Job'), Url::fromPath('x509/jobs/new'), 'plus'))
+ ->openInModal()
+ );
+ }
+
+ $sortControl = $this->createSortControl($jobs, [
+ 'name' => $this->translate('Name'),
+ 'author' => $this->translate('Author'),
+ 'ctime' => $this->translate('Date Created'),
+ 'mtime' => $this->translate('Date Modified')
+ ]);
+
+ $this->controls->getAttributes()->add('class', 'default-layout');
+ $this->addControl($sortControl);
+
+ $this->addContent(new Jobs($jobs));
+ }
+
+ public function newAction()
+ {
+ $this->assertPermission('config/x509');
+
+ $this->addTitleTab($this->translate('New Job'));
+
+ $form = (new JobConfigForm())
+ ->setAction((string) Url::fromRequest())
+ ->on(JobConfigForm::ON_SUCCESS, function () {
+ $this->closeModalAndRefreshRelatedView(Url::fromPath('x509/jobs'));
+ })
+ ->handleRequest($this->getServerRequest());
+
+ $this->addContent($form);
+ }
+}
diff --git a/application/controllers/SniController.php b/application/controllers/SniController.php
new file mode 100644
index 0000000..cde4807
--- /dev/null
+++ b/application/controllers/SniController.php
@@ -0,0 +1,103 @@
+<?php
+
+// Icinga Web 2 X.509 Module | (c) 2018 Icinga GmbH | GPLv2
+
+namespace Icinga\Module\X509\Controllers;
+
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\X509\Forms\Config\SniConfigForm;
+use Icinga\Module\X509\SniIniRepository;
+use ipl\Html\HtmlString;
+use ipl\Web\Compat\CompatController;
+use ipl\Web\Url;
+use ipl\Web\Widget\ButtonLink;
+
+class SniController extends CompatController
+{
+ /**
+ * List all maps
+ */
+ public function indexAction()
+ {
+ $this->getTabs()->add('jobs', [
+ 'title' => $this->translate('Configure Jobs'),
+ 'label' => $this->translate('Jobs'),
+ 'url' => 'x509/jobs',
+ 'baseTarget' => '_main'
+
+ ]);
+ $this->addTitleTab($this->translate('SNI'));
+
+ $this->addControl(
+ (new ButtonLink($this->translate('New SNI Map'), Url::fromPath('x509/sni/new'), 'plus'))
+ ->openInModal()
+ );
+ $this->controls->getAttributes()->add('class', 'default-layout');
+
+ $this->view->controls = $this->controls;
+
+ $repo = new SniIniRepository();
+
+ $this->view->sni = $repo->select(array('ip'));
+ }
+
+ /**
+ * Create a map
+ */
+ public function newAction()
+ {
+ $this->addTitleTab($this->translate('New SNI Map'));
+
+ $form = $this->prepareForm()->add();
+
+ $form->handleRequest();
+
+ $this->addContent(new HtmlString($form->render()));
+ }
+
+ /**
+ * Update a map
+ */
+ public function updateAction()
+ {
+ $form = $this->prepareForm()->edit($this->params->getRequired('ip'));
+
+ try {
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound($this->translate('IP not found'));
+ }
+
+ $this->renderForm($form, $this->translate('Update SNI Map'));
+ }
+
+ /**
+ * Remove a map
+ */
+ public function removeAction()
+ {
+ $form = $this->prepareForm()->remove($this->params->getRequired('ip'));
+
+ try {
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound($this->translate('IP not found'));
+ }
+
+ $this->renderForm($form, $this->translate('Remove SNI Map'));
+ }
+
+ /**
+ * Assert config permission and return a prepared RepositoryForm
+ *
+ * @return SniConfigForm
+ */
+ protected function prepareForm()
+ {
+ $this->assertPermission('config/x509');
+
+ return (new SniConfigForm())
+ ->setRepository(new SniIniRepository())
+ ->setRedirectUrl(Url::fromPath('x509/sni'));
+ }
+}
diff --git a/application/controllers/UsageController.php b/application/controllers/UsageController.php
new file mode 100644
index 0000000..079d24a
--- /dev/null
+++ b/application/controllers/UsageController.php
@@ -0,0 +1,141 @@
+<?php
+
+// Icinga Web 2 X.509 Module | (c) 2018 Icinga GmbH | GPLv2
+
+namespace Icinga\Module\X509\Controllers;
+
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\X509\Common\Database;
+use Icinga\Module\X509\Controller;
+use Icinga\Module\X509\Model\X509Certificate;
+use Icinga\Module\X509\UsageTable;
+use Icinga\Module\X509\Web\Control\SearchBar\ObjectSuggestions;
+use ipl\Orm\Query;
+use ipl\Sql\Expression;
+use ipl\Web\Control\LimitControl;
+use ipl\Web\Control\SortControl;
+
+class UsageController extends Controller
+{
+ public function indexAction()
+ {
+ $this->addTitleTab($this->translate('Certificate Usage'));
+ $this->getTabs()->enableDataExports();
+
+ try {
+ $conn = Database::get();
+ } catch (ConfigurationError $_) {
+ $this->render('missing-resource', null, true);
+ return;
+ }
+
+ $targets = X509Certificate::on($conn)
+ ->with(['chain', 'chain.target'])
+ ->withColumns([
+ 'chain.id',
+ 'chain.valid',
+ 'chain.target.ip',
+ 'chain.target.port',
+ 'chain.target.hostname',
+ ]);
+
+ $targets
+ ->getSelectBase()
+ ->where(new Expression('certificate_link.order = 0'));
+
+ $sortColumns = [
+ 'chain.target.hostname' => $this->translate('Hostname'),
+ 'chain.target.ip' => $this->translate('IP'),
+ 'chain.target.port' => $this->translate('Port'),
+ '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'),
+ 'chain.valid' => $this->translate('Chain Is Valid'),
+ 'duration' => $this->translate('Duration')
+ ];
+
+ $limitControl = $this->createLimitControl();
+ $paginator = $this->createPaginationControl($targets);
+ $sortControl = $this->createSortControl($targets, $sortColumns);
+
+ $searchBar = $this->createSearchBar($targets, [
+ $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();
+ }
+
+ $targets->peekAhead($this->view->compact);
+
+ $targets->filter($filter);
+
+ $this->addControl($paginator);
+ $this->addControl($sortControl);
+ $this->addControl($limitControl);
+ $this->addControl($searchBar);
+
+ $this->handleFormatRequest($targets, function (Query $targets) {
+ /** @var X509Certificate $usage */
+ foreach ($targets as $usage) {
+ $usage->valid_from = $usage->valid_from->format('l F jS, Y H:i:s e');
+ $usage->valid_to = $usage->valid_to->format('l F jS, Y H:i:s e');
+
+ $usage->ip = $usage->chain->target->ip;
+ $usage->hostname = $usage->chain->target->hostname;
+ $usage->port = $usage->chain->target->port;
+ $usage->valid = $usage->chain->valid;
+
+ yield array_intersect_key(
+ iterator_to_array($usage),
+ array_flip(array_merge(['valid', 'hostname', 'ip', 'port'], $usage->getExportableColumns()))
+ );
+ }
+ });
+
+ $this->addContent((new UsageTable())->setData($targets));
+
+ if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
+ $this->sendMultipartUpdate(); // Updates the browser search bar
+ }
+ }
+
+ public function completeAction()
+ {
+ $this->getDocument()->add(
+ (new ObjectSuggestions())
+ ->setModel(X509Certificate::class)
+ ->forRequest($this->getServerRequest())
+ );
+ }
+
+ public function searchEditorAction()
+ {
+ $editor = $this->createSearchEditor(X509Certificate::on(Database::get()), [
+ LimitControl::DEFAULT_LIMIT_PARAM,
+ SortControl::DEFAULT_SORT_PARAM
+ ]);
+
+ $this->getDocument()->add($editor);
+ $this->setTitle(t('Adjust Filter'));
+ }
+}