From 439757d4640851a18732705b5222f5569ce5a73d Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 26 Jul 2016 09:52:55 +0200 Subject: [PATCH] IcingaHost: introduce API keys --- library/Director/Objects/IcingaHost.php | 28 ++++++ schema/mysql-migrations/upgrade_101.sql | 7 ++ schema/mysql.sql | 2 + schema/pgsql-migrations/upgrade_101.sql | 9 ++ schema/pgsql.sql | 2 + .../Director/Objects/IcingaHostTest.php | 86 ++++++++++++++++++- 6 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 schema/mysql-migrations/upgrade_101.sql create mode 100644 schema/pgsql-migrations/upgrade_101.sql diff --git a/library/Director/Objects/IcingaHost.php b/library/Director/Objects/IcingaHost.php index e4118198..a0cb9e86 100644 --- a/library/Director/Objects/IcingaHost.php +++ b/library/Director/Objects/IcingaHost.php @@ -3,6 +3,8 @@ namespace Icinga\Module\Director\Objects; use Icinga\Data\Db\DbConnection; +use Icinga\Exception\NotFoundError; +use Icinga\Module\Director\Db; use Icinga\Module\Director\IcingaConfig\IcingaConfig; use Icinga\Module\Director\Web\Form\DirectorObjectForm; @@ -42,6 +44,7 @@ class IcingaHost extends IcingaObject 'has_agent' => null, 'master_should_connect' => null, 'accept_config' => null, + 'api_key' => null, ); protected $relations = array( @@ -225,6 +228,16 @@ class IcingaHost extends IcingaObject return ''; } + /** + * Internal property, will not be rendered + * + * @return string + */ + protected function renderApi_key() + { + return ''; + } + /** * Internal property, will not be rendered * @@ -235,4 +248,19 @@ class IcingaHost extends IcingaObject // @codingStandardsIgnoreEnd return ''; } + + public static function loadWithApiKey($key, Db $db) + { + $query = $db->getDbAdapter() + ->select() + ->from('icinga_host') + ->where('api_key = ?', $key); + + $result = self::loadAll($db, $query); + if (count($result) !== 1) { + throw new NotFoundError('Got invalid API key "%s"', $key); + } + + return current($result); + } } diff --git a/schema/mysql-migrations/upgrade_101.sql b/schema/mysql-migrations/upgrade_101.sql new file mode 100644 index 00000000..bbe1817e --- /dev/null +++ b/schema/mysql-migrations/upgrade_101.sql @@ -0,0 +1,7 @@ +ALTER TABLE icinga_host + ADD COLUMN api_key VARCHAR(40) DEFAULT NULL AFTER accept_config, + ADD UNIQUE KEY api_key (api_key); + +INSERT INTO director_schema_migration + (schema_version, migration_time) + VALUES (101, NOW()); diff --git a/schema/mysql.sql b/schema/mysql.sql index c6ec60f1..bfdd1a81 100644 --- a/schema/mysql.sql +++ b/schema/mysql.sql @@ -447,8 +447,10 @@ CREATE TABLE icinga_host ( has_agent ENUM('y', 'n') DEFAULT NULL, master_should_connect ENUM('y', 'n') DEFAULT NULL, accept_config ENUM('y', 'n') DEFAULT NULL, + api_key VARCHAR(40) DEFAULT NULL, PRIMARY KEY (id), UNIQUE INDEX object_name (object_name), + UNIQUE INDEX api_key (api_key), KEY search_idx (display_name), CONSTRAINT icinga_host_zone FOREIGN KEY zone (zone_id) diff --git a/schema/pgsql-migrations/upgrade_101.sql b/schema/pgsql-migrations/upgrade_101.sql new file mode 100644 index 00000000..4243c011 --- /dev/null +++ b/schema/pgsql-migrations/upgrade_101.sql @@ -0,0 +1,9 @@ +ALTER TABLE icinga_host + ADD COLUMN api_key character varying(40) DEFAULT NULL; + +CREATE UNIQUE INDEX host_api_key ON icinga_host (api_key); + +INSERT INTO director_schema_migration + (schema_version, migration_time) + VALUES (101, NOW()); + diff --git a/schema/pgsql.sql b/schema/pgsql.sql index 25693867..dab834b7 100644 --- a/schema/pgsql.sql +++ b/schema/pgsql.sql @@ -575,6 +575,7 @@ CREATE TABLE icinga_host ( has_agent enum_boolean DEFAULT NULL, master_should_connect enum_boolean DEFAULT NULL, accept_config enum_boolean DEFAULT NULL, + api_key character varying(40) DEFAULT NULL, PRIMARY KEY (id), CONSTRAINT icinga_host_zone FOREIGN KEY (zone_id) @@ -604,6 +605,7 @@ CREATE TABLE icinga_host ( ); CREATE UNIQUE INDEX object_name_host ON icinga_host (object_name, zone_id); +CREATE UNIQUE INDEX host_api_key ON icinga_host (api_key); CREATE INDEX host_zone ON icinga_host (zone_id); CREATE INDEX host_timeperiod ON icinga_host (check_period_id); CREATE INDEX host_check_command ON icinga_host (check_command_id); diff --git a/test/php/library/Director/Objects/IcingaHostTest.php b/test/php/library/Director/Objects/IcingaHostTest.php index ccf7a584..b8705b07 100644 --- a/test/php/library/Director/Objects/IcingaHostTest.php +++ b/test/php/library/Director/Objects/IcingaHostTest.php @@ -6,6 +6,7 @@ use Icinga\Module\Director\IcingaConfig\IcingaConfig; use Icinga\Module\Director\Objects\IcingaHost; use Icinga\Module\Director\Objects\IcingaZone; use Icinga\Module\Director\Test\BaseTestCase; +use Icinga\Exception\IcingaException; class IcingaHostTest extends BaseTestCase { @@ -392,6 +393,89 @@ class IcingaHostTest extends BaseTestCase } + public function testWhetherTwoHostsCannotBeStoredWithTheSameApiKey() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + $a = IcingaHost::create(array( + 'object_name' => '___TEST___a', + 'object_type' => 'object', + 'api_key' => 'a' + ), $db); + $b = IcingaHost::create(array( + 'object_name' => '___TEST___b', + 'object_type' => 'object', + 'api_key' => 'a' + ), $db); + + $a->store(); + try { + $b->store(); + } catch (IcingaException $e) { + $msg = $e->getMessage(); + $matchMysql = strpos( + $msg, + "Duplicate entry 'a' for key 'api_key'" + ) !== false; + + $matchPostgres = strpos( + $msg, + 'Unique violation' + ) !== false; + + $this->assertTrue( + $matchMysql || $matchPostgres, + 'Exception message does not tell about unique constraint violation' + ); + $a->delete(); + } + } + + public function testWhetherHostCanBeLoadedWithValidApiKey() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + $a = IcingaHost::create(array( + 'object_name' => '___TEST___a', + 'object_type' => 'object', + 'api_key' => 'a1a1a1' + ), $db); + $b = IcingaHost::create(array( + 'object_name' => '___TEST___b', + 'object_type' => 'object', + 'api_key' => 'b1b1b1' + ), $db); + $a->store(); + $b->store(); + + $this->assertEquals( + IcingaHost::loadWithApiKey('b1b1b1', $db)->object_name, + '___TEST___b' + ); + + $a->delete(); + $b->delete(); + } + + /** + * @expectedException \Icinga\Exception\NotFoundError + */ + public function testWhetherInvalidApiKeyThrows404() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + IcingaHost::loadWithApiKey('No___such___key', $db); + } + protected function getDummyRelatedProperties() { return array( @@ -434,7 +518,7 @@ class IcingaHostTest extends BaseTestCase { if ($this->hasDb()) { $db = $this->getDb(); - $kill = array($this->testHostName, '___TEST___parent'); + $kill = array($this->testHostName, '___TEST___parent', '___TEST___a', '___TEST___b'); foreach ($kill as $name) { if (IcingaHost::exists($name, $db)) { IcingaHost::load($name, $db)->delete();