diff options
Diffstat (limited to 'application/controllers')
-rw-r--r-- | application/controllers/CertificateController.php | 43 | ||||
-rw-r--r-- | application/controllers/CertificatesController.php | 117 | ||||
-rw-r--r-- | application/controllers/ChainController.php | 77 | ||||
-rw-r--r-- | application/controllers/ConfigController.php | 30 | ||||
-rw-r--r-- | application/controllers/DashboardController.php | 153 | ||||
-rw-r--r-- | application/controllers/JobController.php | 226 | ||||
-rw-r--r-- | application/controllers/JobsController.php | 66 | ||||
-rw-r--r-- | application/controllers/SniController.php | 103 | ||||
-rw-r--r-- | application/controllers/UsageController.php | 141 |
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')); + } +} |