summaryrefslogtreecommitdiffstats
path: root/library/Director/Import/ImportSourceRestApi.php
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--library/Director/Import/ImportSourceRestApi.php380
1 files changed, 380 insertions, 0 deletions
diff --git a/library/Director/Import/ImportSourceRestApi.php b/library/Director/Import/ImportSourceRestApi.php
new file mode 100644
index 0000000..dc772e1
--- /dev/null
+++ b/library/Director/Import/ImportSourceRestApi.php
@@ -0,0 +1,380 @@
+<?php
+
+namespace Icinga\Module\Director\Import;
+
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Module\Director\Hook\ImportSourceHook;
+use Icinga\Module\Director\RestApi\RestApiClient;
+use Icinga\Module\Director\Web\Form\QuickForm;
+use InvalidArgumentException;
+
+class ImportSourceRestApi extends ImportSourceHook
+{
+ public function getName()
+ {
+ return 'REST API';
+ }
+
+ public function fetchData()
+ {
+ $result = $this->getRestApi()->get(
+ $this->getUrl(),
+ null,
+ $this->buildHeaders()
+ );
+ $result = $this->extractProperty($result);
+
+ return (array) $result;
+ }
+
+ public function listColumns()
+ {
+ $rows = $this->fetchData();
+ $columns = [];
+
+ foreach ($rows as $object) {
+ foreach (array_keys((array) $object) as $column) {
+ if (! isset($columns[$column])) {
+ $columns[] = $column;
+ }
+ }
+ }
+
+ return $columns;
+ }
+
+ /**
+ * Extract result from a property specified
+ *
+ * A simple key, like "objects", will take the final result from key objects
+ *
+ * If you have a deeper key like "objects" under the key "results", specify this as "results.objects".
+ *
+ * When a key of the JSON object contains a literal ".", this can be escaped as
+ *
+ * @param $result
+ *
+ * @return mixed
+ */
+ protected function extractProperty($result)
+ {
+ $property = $this->getSetting('extract_property');
+ if (! $property) {
+ return $result;
+ }
+
+ $parts = preg_split('~(?<!\\\\)\.~', $property);
+
+ // iterate over parts of the attribute path
+ $data = $result;
+ foreach ($parts as $part) {
+ // un-escape any dots
+ $part = preg_replace('~\\\\.~', '.', $part);
+
+ if (property_exists($data, $part)) {
+ $data = $data->$part;
+ } else {
+ throw new \RuntimeException(sprintf(
+ 'Result has no "%s" property. Available keys: %s',
+ $part,
+ implode(', ', array_keys((array) $data))
+ ));
+ }
+ }
+
+ return $data;
+ }
+
+ protected function buildHeaders()
+ {
+ $headers = [];
+
+ $text = $this->getSetting('headers', '');
+ foreach (preg_split('~\r?\n~', $text, -1, PREG_SPLIT_NO_EMPTY) as $header) {
+ $header = trim($header);
+ $parts = preg_split('~\s*:\s*~', $header, 2);
+ if (count($parts) < 2) {
+ throw new InvalidPropertyException('Could not parse header: "%s"', $header);
+ }
+
+ $headers[$parts[0]] = $parts[1];
+ }
+
+ return $headers;
+ }
+
+ /**
+ * @param QuickForm $form
+ * @throws \Zend_Form_Exception
+ */
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ static::addScheme($form);
+ static::addSslOptions($form);
+ static::addUrl($form);
+ static::addResultProperty($form);
+ static::addAuthentication($form);
+ static::addHeader($form);
+ static::addProxy($form);
+ }
+
+ /**
+ * @param QuickForm $form
+ * @throws \Zend_Form_Exception
+ */
+ protected static function addScheme(QuickForm $form)
+ {
+ $form->addElement('select', 'scheme', [
+ 'label' => $form->translate('Protocol'),
+ 'description' => $form->translate(
+ 'Whether to use encryption when talking to the REST API'
+ ),
+ 'multiOptions' => [
+ 'HTTPS' => $form->translate('HTTPS (strongly recommended)'),
+ 'HTTP' => $form->translate('HTTP (this is plaintext!)'),
+ ],
+ 'class' => 'autosubmit',
+ 'value' => 'HTTPS',
+ 'required' => true,
+ ]);
+ }
+
+ /**
+ * @param QuickForm $form
+ * @throws \Zend_Form_Exception
+ */
+ protected static function addHeader(QuickForm $form)
+ {
+ $form->addElement('textarea', 'headers', [
+ 'label' => $form->translate('HTTP Header'),
+ 'description' => implode(' ', [
+ $form->translate('Additional headers for the HTTP request.'),
+ $form->translate('Specify headers in text format "Header: Value", each header on a new line.'),
+ ]),
+ 'class' => 'preformatted',
+ 'rows' => 4,
+ ]);
+ }
+
+ /**
+ * @param QuickForm $form
+ * @throws \Zend_Form_Exception
+ */
+ protected static function addSslOptions(QuickForm $form)
+ {
+ $ssl = ! ($form->getSentOrObjectSetting('scheme', 'HTTPS') === 'HTTP');
+
+ if ($ssl) {
+ static::addBoolean($form, 'ssl_verify_peer', [
+ 'label' => $form->translate('Verify Peer'),
+ 'description' => $form->translate(
+ 'Whether we should check that our peer\'s certificate has'
+ . ' been signed by a trusted CA. This is strongly recommended.'
+ )
+ ], 'y');
+ static::addBoolean($form, 'ssl_verify_host', [
+ 'label' => $form->translate('Verify Host'),
+ 'description' => $form->translate(
+ 'Whether we should check that the certificate matches the'
+ . 'configured host'
+ )
+ ], 'y');
+ }
+ }
+
+ /**
+ * @param QuickForm $form
+ * @throws \Zend_Form_Exception
+ */
+ protected static function addUrl(QuickForm $form)
+ {
+ $form->addElement('text', 'url', [
+ 'label' => 'REST API URL',
+ 'description' => $form->translate(
+ 'Something like https://api.example.com/rest/v2/objects'
+ ),
+ 'required' => true,
+ ]);
+ }
+
+ /**
+ * @param QuickForm $form
+ * @throws \Zend_Form_Exception
+ */
+ protected static function addResultProperty(QuickForm $form)
+ {
+ $form->addElement('text', 'extract_property', [
+ 'label' => 'Extract property',
+ 'description' => implode("\n", [
+ $form->translate('Often the expected result is provided in a property like "objects".'
+ . ' Please specify this if required.'),
+ $form->translate('Also deeper keys can be specific by a dot-notation:'),
+ '"result.objects", "key.deeper_key.very_deep"',
+ $form->translate('Literal dots in a key name can be written in the escape notation:'),
+ '"key\.with\.dots"',
+ ])
+ ]);
+ }
+
+ /**
+ * @param QuickForm $form
+ * @throws \Zend_Form_Exception
+ */
+ protected static function addAuthentication(QuickForm $form)
+ {
+ $form->addElement('text', 'username', [
+ 'label' => $form->translate('Username'),
+ 'description' => $form->translate(
+ 'Will be used to authenticate against your REST API'
+ ),
+ ]);
+
+ $form->addElement('storedPassword', 'password', [
+ 'label' => $form->translate('Password'),
+ ]);
+ }
+
+ /**
+ * @param QuickForm $form
+ * @throws \Zend_Form_Exception
+ */
+ protected static function addProxy(QuickForm $form)
+ {
+ $form->addElement('select', 'proxy_type', [
+ 'label' => $form->translate('Proxy'),
+ 'description' => $form->translate(
+ 'In case your API is only reachable through a proxy, please'
+ . ' choose it\'s protocol right here'
+ ),
+ 'multiOptions' => $form->optionalEnum([
+ 'HTTP' => $form->translate('HTTP proxy'),
+ 'SOCKS5' => $form->translate('SOCKS5 proxy'),
+ ]),
+ 'class' => 'autosubmit'
+ ]);
+
+ $proxyType = $form->getSentOrObjectSetting('proxy_type');
+
+ if ($proxyType) {
+ $form->addElement('text', 'proxy', [
+ 'label' => $form->translate('Proxy Address'),
+ 'description' => $form->translate(
+ 'Hostname, IP or <host>:<port>'
+ ),
+ 'required' => true,
+ ]);
+ if ($proxyType === 'HTTP') {
+ $form->addElement('text', 'proxy_user', [
+ 'label' => $form->translate('Proxy Username'),
+ 'description' => $form->translate(
+ 'In case your proxy requires authentication, please'
+ . ' configure this here'
+ ),
+ ]);
+
+ $passRequired = strlen($form->getSentOrObjectSetting('proxy_user')) > 0;
+
+ $form->addElement('storedPassword', 'proxy_pass', [
+ 'label' => $form->translate('Proxy Password'),
+ 'required' => $passRequired
+ ]);
+ }
+ }
+ }
+
+ protected function getUrl()
+ {
+ $url = $this->getSetting('url');
+ $parts = \parse_url($url);
+ if (isset($parts['path'])) {
+ $path = $parts['path'];
+ } else {
+ $path = '/';
+ }
+
+ if (isset($parts['query'])) {
+ $url = "$path?" . $parts['query'];
+ } else {
+ $url = $path;
+ }
+
+ return $url;
+ }
+
+ protected function getRestApi()
+ {
+ $url = $this->getSetting('url');
+ $parts = \parse_url($url);
+ if (isset($parts['host'])) {
+ $host = $parts['host'];
+ } else {
+ throw new InvalidArgumentException("URL '$url' has no host");
+ }
+
+ $api = new RestApiClient(
+ $host,
+ $this->getSetting('username'),
+ $this->getSetting('password')
+ );
+
+ $api->setScheme($this->getSetting('scheme'));
+ if (isset($parts['port'])) {
+ $api->setPort($parts['port']);
+ }
+
+ if ($api->getScheme() === 'HTTPS') {
+ if ($this->getSetting('ssl_verify_peer', 'y') === 'n') {
+ $api->disableSslPeerVerification();
+ }
+ if ($this->getSetting('ssl_verify_host', 'y') === 'n') {
+ $api->disableSslHostVerification();
+ }
+ }
+
+ if ($proxy = $this->getSetting('proxy')) {
+ if ($proxyType = $this->getSetting('proxy_type')) {
+ $api->setProxy($proxy, $proxyType);
+ } else {
+ $api->setProxy($proxy);
+ }
+
+ if ($user = $this->getSetting('proxy_user')) {
+ $api->setProxyAuth($user, $this->getSetting('proxy_pass'));
+ }
+ }
+
+ return $api;
+ }
+
+ /**
+ * @param QuickForm $form
+ * @param string $key
+ * @param array $options
+ * @param string|null $default
+ * @throws \Zend_Form_Exception
+ */
+ protected static function addBoolean(QuickForm $form, $key, $options, $default = null)
+ {
+ if ($default === null) {
+ $form->addElement('OptionalYesNo', $key, $options);
+ } else {
+ $form->addElement('YesNo', $key, $options);
+ $form->getElement($key)->setValue($default);
+ }
+ }
+
+ /**
+ * @param QuickForm $form
+ * @param string $key
+ * @param string $label
+ * @param string $description
+ * @throws \Zend_Form_Exception
+ */
+ protected static function optionalBoolean(QuickForm $form, $key, $label, $description)
+ {
+ static::addBoolean($form, $key, [
+ 'label' => $label,
+ 'description' => $description
+ ]);
+ }
+}