mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-27 07:44:04 +02:00
Merge pull request #2748 from Icinga/bugfix/validate-icinga2-api-user-2674
Bugfix/validate icinga2 api user 2674
This commit is contained in:
commit
efac7f44c9
@ -64,7 +64,8 @@ class ApiTransportForm extends Form
|
|||||||
'description' => $this->translate(
|
'description' => $this->translate(
|
||||||
'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be'
|
'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be'
|
||||||
. ' possible for this user'
|
. ' possible for this user'
|
||||||
)
|
),
|
||||||
|
'renderPassword' => true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
namespace Icinga\Module\Monitoring\Forms\Config;
|
namespace Icinga\Module\Monitoring\Forms\Config;
|
||||||
|
|
||||||
|
use Icinga\Data\ConfigObject;
|
||||||
|
use Icinga\Module\Monitoring\Command\Transport\CommandTransport;
|
||||||
|
use Icinga\Module\Monitoring\Exception\CommandTransportException;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Icinga\Application\Platform;
|
use Icinga\Application\Platform;
|
||||||
use Icinga\Exception\IcingaException;
|
use Icinga\Exception\IcingaException;
|
||||||
@ -34,6 +37,11 @@ class TransportConfigForm extends ConfigForm
|
|||||||
*/
|
*/
|
||||||
protected $instanceNames;
|
protected $instanceNames;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $validatePartial = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize this form
|
* Initialize this form
|
||||||
*/
|
*/
|
||||||
@ -250,6 +258,59 @@ class TransportConfigForm extends ConfigForm
|
|||||||
$this->addSubForm($this->getTransportForm($transportType)->create($formData), 'transport_form');
|
$this->addSubForm($this->getTransportForm($transportType)->create($formData), 'transport_form');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a submit button to this form and one to manually validate the configuration
|
||||||
|
*
|
||||||
|
* Calls parent::addSubmitButton() to add the submit button.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addSubmitButton()
|
||||||
|
{
|
||||||
|
parent::addSubmitButton();
|
||||||
|
|
||||||
|
if ($this->getSubForm('transport_form') instanceof ApiTransportForm) {
|
||||||
|
$this->getElement('btn_submit')
|
||||||
|
->setDecorators(array('ViewHelper'));
|
||||||
|
|
||||||
|
$this->addElement(
|
||||||
|
'submit',
|
||||||
|
'transport_validation',
|
||||||
|
array(
|
||||||
|
'ignore' => true,
|
||||||
|
'label' => $this->translate('Validate Configuration'),
|
||||||
|
'data-progress-label' => $this->translate('Validation In Progress'),
|
||||||
|
'decorators' => array('ViewHelper')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->setAttrib('data-progress-element', 'transport-progress');
|
||||||
|
$this->addElement(
|
||||||
|
'note',
|
||||||
|
'transport-progress',
|
||||||
|
array(
|
||||||
|
'decorators' => array(
|
||||||
|
'ViewHelper',
|
||||||
|
array('Spinner', array('id' => 'transport-progress'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->addDisplayGroup(
|
||||||
|
array('btn_submit', 'transport_validation', 'transport-progress'),
|
||||||
|
'submit_validation',
|
||||||
|
array(
|
||||||
|
'decorators' => array(
|
||||||
|
'FormElements',
|
||||||
|
array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate the configuration of the transport to load
|
* Populate the configuration of the transport to load
|
||||||
*/
|
*/
|
||||||
@ -261,4 +322,57 @@ class TransportConfigForm extends ConfigForm
|
|||||||
$this->populate($data);
|
$this->populate($data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isValidPartial(array $formData)
|
||||||
|
{
|
||||||
|
$isValidPartial = parent::isValidPartial($formData);
|
||||||
|
|
||||||
|
$transportValidation = $this->getElement('transport_validation');
|
||||||
|
if ($transportValidation !== null && $transportValidation->isChecked() && $this->isValid($formData)) {
|
||||||
|
$this->info($this->translate('The configuration has been successfully validated.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $isValidPartial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isValid($formData)
|
||||||
|
{
|
||||||
|
if (! parent::isValid($formData)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! ($this->getElement('transport_validation') === null || (
|
||||||
|
$this->isSubmitted() && isset($formData['force_creation']) && $formData['force_creation'])
|
||||||
|
)) {
|
||||||
|
try {
|
||||||
|
CommandTransport::createTransport(new ConfigObject($this->getValues()))->probe();
|
||||||
|
} catch (CommandTransportException $e) {
|
||||||
|
$this->error(sprintf(
|
||||||
|
$this->translate('Failed to successfully validate the configuration: %s'),
|
||||||
|
$e->getMessage()
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->addElement(
|
||||||
|
'checkbox',
|
||||||
|
'force_creation',
|
||||||
|
array(
|
||||||
|
'order' => 0,
|
||||||
|
'ignore' => true,
|
||||||
|
'label' => $this->translate('Force Changes'),
|
||||||
|
'description' => $this->translate('Check this box to enforce changes without connectivity validation')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ use Icinga\Module\Monitoring\Command\IcingaApiCommand;
|
|||||||
use Icinga\Module\Monitoring\Command\IcingaCommand;
|
use Icinga\Module\Monitoring\Command\IcingaCommand;
|
||||||
use Icinga\Module\Monitoring\Command\Renderer\IcingaApiCommandRenderer;
|
use Icinga\Module\Monitoring\Command\Renderer\IcingaApiCommandRenderer;
|
||||||
use Icinga\Module\Monitoring\Exception\CommandTransportException;
|
use Icinga\Module\Monitoring\Exception\CommandTransportException;
|
||||||
|
use Icinga\Module\Monitoring\Exception\CurlException;
|
||||||
use Icinga\Module\Monitoring\Web\Rest\RestRequest;
|
use Icinga\Module\Monitoring\Web\Rest\RestRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -238,4 +239,32 @@ class ApiCommandTransport implements CommandTransportInterface
|
|||||||
{
|
{
|
||||||
$this->sendCommand($this->renderer->render($command));
|
$this->sendCommand($this->renderer->render($command));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to connect to the API
|
||||||
|
*
|
||||||
|
* @throws CommandTransportException In case of failure
|
||||||
|
*/
|
||||||
|
public function probe()
|
||||||
|
{
|
||||||
|
$request = RestRequest::get($this->getUriFor(null))
|
||||||
|
->authenticateWith($this->getUsername(), $this->getPassword())
|
||||||
|
->noStrictSsl();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $request->send();
|
||||||
|
} catch (CurlException $e) {
|
||||||
|
throw new CommandTransportException('Couldn\'t connect to the Icinga 2 API: %s', $e->getMessage());
|
||||||
|
} catch (JsonDecodeException $e) {
|
||||||
|
throw new CommandTransportException('Got invalid JSON response from the Icinga 2 API: %s', $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($response['error'])) {
|
||||||
|
throw new CommandTransportException(
|
||||||
|
'Can\'t connect to the Icinga 2 API: %u %s',
|
||||||
|
$response['error'],
|
||||||
|
$response['status']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Module\Monitoring\Exception;
|
||||||
|
|
||||||
|
use Icinga\Exception\IcingaException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown if {@link curl_exec()} fails
|
||||||
|
*/
|
||||||
|
class CurlException extends IcingaException
|
||||||
|
{
|
||||||
|
}
|
@ -6,6 +6,7 @@ namespace Icinga\Module\Monitoring\Web\Rest;
|
|||||||
use Exception;
|
use Exception;
|
||||||
use Icinga\Application\Logger;
|
use Icinga\Application\Logger;
|
||||||
use Icinga\Util\Json;
|
use Icinga\Util\Json;
|
||||||
|
use Icinga\Module\Monitoring\Exception\CurlException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST Request
|
* REST Request
|
||||||
@ -75,6 +76,21 @@ class RestRequest
|
|||||||
*/
|
*/
|
||||||
protected $timeout = 30;
|
protected $timeout = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a GET REST request
|
||||||
|
*
|
||||||
|
* @param string $uri
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function get($uri)
|
||||||
|
{
|
||||||
|
$request = new static;
|
||||||
|
$request->uri = $uri;
|
||||||
|
$request->method = 'GET';
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a POST REST request
|
* Create a POST REST request
|
||||||
*
|
*
|
||||||
@ -197,15 +213,12 @@ class RestRequest
|
|||||||
'Expect:'
|
'Expect:'
|
||||||
);
|
);
|
||||||
|
|
||||||
$ch = curl_init();
|
|
||||||
|
|
||||||
$options = array(
|
$options = array(
|
||||||
CURLOPT_URL => $this->uri,
|
CURLOPT_URL => $this->uri,
|
||||||
CURLOPT_TIMEOUT => $this->timeout,
|
CURLOPT_TIMEOUT => $this->timeout,
|
||||||
// Ignore proxy settings
|
// Ignore proxy settings
|
||||||
CURLOPT_PROXY => '',
|
CURLOPT_PROXY => '',
|
||||||
CURLOPT_CUSTOMREQUEST => $this->method,
|
CURLOPT_CUSTOMREQUEST => $this->method
|
||||||
CURLOPT_RETURNTRANSFER => true
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Record cURL command line for debugging
|
// Record cURL command line for debugging
|
||||||
@ -234,27 +247,20 @@ class RestRequest
|
|||||||
$options[CURLOPT_HTTPHEADER] = $headers;
|
$options[CURLOPT_HTTPHEADER] = $headers;
|
||||||
|
|
||||||
$stream = null;
|
$stream = null;
|
||||||
if (Logger::getInstance()->getLevel() === Logger::DEBUG) {
|
$logger = Logger::getInstance();
|
||||||
|
if ($logger !== null && $logger->getLevel() === Logger::DEBUG) {
|
||||||
$stream = fopen('php://temp', 'w');
|
$stream = fopen('php://temp', 'w');
|
||||||
$options[CURLOPT_VERBOSE] = true;
|
$options[CURLOPT_VERBOSE] = true;
|
||||||
$options[CURLOPT_STDERR] = $stream;
|
$options[CURLOPT_STDERR] = $stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
curl_setopt_array($ch, $options);
|
|
||||||
|
|
||||||
Logger::debug(
|
Logger::debug(
|
||||||
'Executing %s %s',
|
'Executing %s %s',
|
||||||
implode(' ', $curlCmd),
|
implode(' ', $curlCmd),
|
||||||
escapeshellarg($this->uri)
|
escapeshellarg($this->uri)
|
||||||
);
|
);
|
||||||
|
|
||||||
$result = curl_exec($ch);
|
$result = $this->curlExec($options);
|
||||||
|
|
||||||
if ($result === false) {
|
|
||||||
throw new Exception(curl_error($ch));
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
if (is_resource($stream)) {
|
if (is_resource($stream)) {
|
||||||
rewind($stream);
|
rewind($stream);
|
||||||
@ -264,4 +270,28 @@ class RestRequest
|
|||||||
|
|
||||||
return Json::decode($result, true);
|
return Json::decode($result, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up a new cURL handle with the given options and call {@link curl_exec()}
|
||||||
|
*
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return string The response
|
||||||
|
*
|
||||||
|
* @throws CurlException
|
||||||
|
*/
|
||||||
|
protected function curlExec(array $options)
|
||||||
|
{
|
||||||
|
$ch = curl_init();
|
||||||
|
$options[CURLOPT_RETURNTRANSFER] = true;
|
||||||
|
curl_setopt_array($ch, $options);
|
||||||
|
$result = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($result === false) {
|
||||||
|
throw new CurlException('%s', curl_error($ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Tests\Icinga\Modules\Monitoring\Web\Rest;
|
||||||
|
|
||||||
|
use Icinga\Exception\Json\JsonDecodeException;
|
||||||
|
use Icinga\Module\Monitoring\Web\Rest\RestRequest;
|
||||||
|
use Icinga\Test\BaseTestCase;
|
||||||
|
|
||||||
|
class MockedRestRequest extends RestRequest
|
||||||
|
{
|
||||||
|
protected function curlExec(array $options)
|
||||||
|
{
|
||||||
|
return '<h1>Unauthorized</h1>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RestRequestTest extends BaseTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @expectedException JsonDecodeException
|
||||||
|
*/
|
||||||
|
public function testInvalidServerResponseHandling()
|
||||||
|
{
|
||||||
|
MockedRestRequest::get('http://localhost')->send();
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user