From effc74e2dfd4f1496fe6a684f56cbe65bc67c9b3 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Mon, 24 Sep 2018 21:14:09 +0200 Subject: [PATCH 1/3] feat(agent): give ability to update an existing agent Signed-off-by: Thierry Bugier --- inc/agent.class.php | 55 +++++++++++++- install/install.class.php | 2 +- install/upgrade_to_dev.php | 11 ++- tests/suite-integration/ProfileRight.php | 2 +- tests/suite-unit/PluginFlyvemdmAgent.php | 93 ++++++++++++++++++++++++ 5 files changed, 158 insertions(+), 5 deletions(-) diff --git a/inc/agent.class.php b/inc/agent.class.php index 9938523c..96509809 100644 --- a/inc/agent.class.php +++ b/inc/agent.class.php @@ -386,6 +386,20 @@ public function canViewItem() { return $_SESSION['glpiID'] == $computer->getField('users_id'); } + public function canUpdateItem() { + // Check the active profile + $config = Config::getConfigurationValues('flyvemdm', ['guest_profiles_id']); + if ($_SESSION['glpiactiveprofile']['id'] != $config['guest_profiles_id']) { + return parent::canUpdateItem(); + } + + if (!$this->checkEntity(true)) { + return false; + } + + return $_SESSION['glpiID'] == $this->fields[User::getForeignKeyField()]; + } + /** * Sends a wipe command to the agent */ @@ -469,6 +483,11 @@ public function prepareInputForAdd($input) { } public function prepareInputForUpdate($input) { + $config = Config::getConfigurationValues('flyvemdm', ['guest_profiles_id']); + if ($_SESSION['glpiactiveprofile']['id'] == $config['guest_profiles_id']) { + return $this->prepareInputForUpdateFromDevice($input); + } + if (isset($input['plugin_flyvemdm_fleets_id'])) { // Update MQTT ACL for the fleet $oldFleet = new PluginFlyvemdmFleet(); @@ -547,6 +566,31 @@ public function prepareInputForUpdate($input) { return $input; } + /** + * Prepare input for update from the agent itseld + * + * @param array $input + * @return array + */ + private function prepareInputForUpdateFromDevice($input) { + //Sanitize input + unset($input[Computer::getForeignKeyField()]); + unset($input[User::getForeignKeyField()]); + unset($input[Entity::getForeignKeyField()]); + unset($input[PluginFlyvemdmFleet::getForeignKeyField()]); + unset($input['name']); + unset($input['wipe']); + unset($input['lock']); + unset($input['enroll_status']); + unset($input['last_report']); + unset($input['last_contact']); + unset($input['is_online']); + unset($input['certificate']); + unset($input['mdm_type']); + + return $input; + } + public function post_addItem() { // Notify the agent about its fleets $this->updateSubscription(); @@ -1294,6 +1338,15 @@ protected function enrollByInvitationToken($input) { } $computerId = $pfAgent->getField(Computer::getForeignKeyField()); + // Check no Flyvemdm agent is linked to this computer + $agent = new self(); + if ($agent->getFromDBByCrit(['computers_id' => $computerId])) { + // Save the agent ID in session to allow the device to find it + // and update it. Give up creation + $_SESSION['plugin_flyvemdm_agents_id'] = $agent->getID(); + return false; + } + if ($computerId === 0) { $event = __("Cannot create the device", 'flyvemdm'); $this->filterMessages($event); @@ -1355,7 +1408,7 @@ protected function enrollByInvitationToken($input) { // Create the agent $defaultFleet = PluginFlyvemdmFleet::getDefaultFleet(); if ($defaultFleet === null) { - $event = __("No default fleet available for the device", 'flyvemdm'); + $event = __('No default fleet available for the device', 'flyvemdm'); $this->filterMessages($event); $this->logInvitationEvent($invitation, $event); return false; diff --git a/install/install.class.php b/install/install.class.php index a2964f20..5f4e692e 100644 --- a/install/install.class.php +++ b/install/install.class.php @@ -272,7 +272,7 @@ protected function createGuestProfileAccess() { Config::setConfigurationValues('flyvemdm', ['guest_profiles_id' => $profileId]); $profileRight = new ProfileRight(); $profileRight->updateProfileRights($profileId, [ - PluginFlyvemdmAgent::$rightname => READ | CREATE, + PluginFlyvemdmAgent::$rightname => READ | CREATE | UPDATE, PluginFlyvemdmFile::$rightname => READ, PluginFlyvemdmPackage::$rightname => READ, ]); diff --git a/install/upgrade_to_dev.php b/install/upgrade_to_dev.php index f3b334b8..6c2edd6c 100644 --- a/install/upgrade_to_dev.php +++ b/install/upgrade_to_dev.php @@ -42,8 +42,6 @@ function upgrade(Migration $migration) { $migration->setVersion(PLUGIN_FLYVEMDM_VERSION); - $profileRight = new ProfileRight(); - $config = Config::getConfigurationValues('flyvemdm'); if (!isset($config['mqtt_broker_port_backend'])) { // Split port setting for client in one hand and backend in the other hand @@ -51,5 +49,14 @@ function upgrade(Migration $migration) { $config['mqtt_broker_port_backend'] = $config['mqtt_broker_port']; Config::setConfigurationValues('flyvemdm', $config); } + + // Merge new rights into guest profile + $profileId = $config['guest_profiles_id']; + $currentRights = ProfileRight::getProfileRights($profileId); + $newRights = array_merge($currentRights, [ + PluginFlyvemdmAgent::$rightname => CREATE| READ | UPDATE , + ]); + $profileRight = new ProfileRight(); + $profileRight->updateProfileRights($profileId, $newRights); } } diff --git a/tests/suite-integration/ProfileRight.php b/tests/suite-integration/ProfileRight.php index fee9645b..2a024cb8 100644 --- a/tests/suite-integration/ProfileRight.php +++ b/tests/suite-integration/ProfileRight.php @@ -133,7 +133,7 @@ public function testGuestProfileRights() { $profileId = $config['guest_profiles_id']; // Expected rights $rightsSet = [ - \PluginFlyvemdmAgent::$rightname => READ | CREATE, + \PluginFlyvemdmAgent::$rightname => READ | CREATE | UPDATE, \PluginFlyvemdmPackage::$rightname => READ, \PluginFlyvemdmFile::$rightname => READ, ]; diff --git a/tests/suite-unit/PluginFlyvemdmAgent.php b/tests/suite-unit/PluginFlyvemdmAgent.php index 53549371..a02d887c 100644 --- a/tests/suite-unit/PluginFlyvemdmAgent.php +++ b/tests/suite-unit/PluginFlyvemdmAgent.php @@ -348,4 +348,97 @@ public function testGetSpecificValueToDisplay() { $this->string($instance->getSpecificValueToDisplay('mdm_type', 'android'))->contains('Android'); } + + public function testCanUpdateItem() { + // Check a super admin can update the agent + $this->login('glpi', 'glpi'); + $agent = $this->createAgent(); + $output = $agent->canUpdateItem(); + $this->boolean($output)->isTrue(); + + // Check if the account of the agent can update it + $user = new \User(); + $user->getFromDB($agent->fields[\User::getForeignKeyField()]); + $this->loginWithUserToken($user->fields['api_token']); + $output = $agent->canUpdateItem(); + $this->boolean($output)->isTrue(); + + // Check if a not authorized profile can update the item + $this->login('post-only', 'postonly'); + $output = $agent->canUpdateItem(); + $this->boolean($output)->isTrue(); // The check is only on entity restriction + + // Check that canUpdate will prevent update + $output = \PluginFlyvemdmAgent::canUpdate(); + $this->boolean($output)->isFalse(); + } + + /** + * @tags testPrepareInputForUpdate + */ + public function testPrepareInputForUpdate() { + $this->login('glpi', 'glpi'); + $agent = $this->createAgent(); + list($user, $serial, $guestEmail, $invitation) = $this->createUserInvitation(\User::getForeignKeyField()); + $this->loginWithUserToken($user->fields['api_token']); + $output = $agent->prepareInputForUpdate([ + \Computer::getForeignKeyField() => '0', + \User::getForeignKeyField() => '0', + \Entity::getForeignKeyField() => '0', + \PluginFlyvemdmFleet::getForeignKeyField() => '0', + 'name' => '', + 'wipe' => '0', + 'lock' => '0', + 'enroll_status' => '', + 'last_report' => '', + 'last_contact' => '', + 'is_online' => '', + 'certificate' => '', + 'mdm_type' => '', + ]); + + $this->array($output)->notHasKeys([ + \Computer::getForeignKeyField(), + \User::getForeignKeyField(), + \Entity::getForeignKeyField(), + \PluginFlyvemdmFleet::getForeignKeyField(), + 'name', + 'wipe', + 'lock', + 'enroll_status', + 'last_report', + 'last_contact', + 'is_online', + 'certificate', + 'mdm_type', + ]); + } + + /** + * @tags testPrepareInputForAdd + */ + public function testPrepareInputForAdd() { + list($user, $serial, $guestEmail, $invitation) = $this->createUserInvitation(\User::getForeignKeyField()); + list($user2, $serial2, $guestEmail2, $invitation2) = $this->createUserInvitation(\User::getForeignKeyField()); + $invitationToken = $invitation->fields['invitation_token']; + $invitationToken2 = $invitation2->fields['invitation_token']; + $inventory = self::AgentXmlInventory($serial); + $agent = $this->agentFromInvitation($user, $guestEmail, $serial, $invitationToken, 'android', + '', $inventory, [], true); + $this->array($_SESSION)->notHasKey('plugin_flyvemdm_agents_id'); + + // Inconsistency : creating an invitation for tests should not generate a serial + // the serial should be crated in the caller instead + list($user, $serial, $guestEmail, $invitation) = $this->createUserInvitation(\User::getForeignKeyField()); + $newAgent = $this->agentFromInvitation($user2, $guestEmail2, $serial, $invitationToken2, 'android', + '', $inventory, [], false); + // Check the agent is not created + $this->boolean($newAgent->isNewItem())->isTrue(); + // Do not test the message in session: it may be filtered when + // debug mode for enrollment is disabled + // Instead, let's check if the session contains the ID of the agent to update + $this->array($_SESSION)->hasKey('plugin_flyvemdm_agents_id'); + $this->integer((int) $_SESSION['plugin_flyvemdm_agents_id']) + ->isEqualTo($agent->getID()); + } } From 1f48459c27f989812cc15cda0b410adfa04e8ea8 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Wed, 17 Oct 2018 21:26:38 +0200 Subject: [PATCH 2/3] refactor(travis): factorize patch arguments Also prevents patch to detect already applied patch and revert them Signed-off-by: Thierry Bugier --- tests/before_script.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/before_script.sh b/tests/before_script.sh index 4a835f1d..b3d4ea74 100755 --- a/tests/before_script.sh +++ b/tests/before_script.sh @@ -34,18 +34,20 @@ mkdir plugins/fusioninventory && git clone --depth=35 $FI_SOURCE -b $FI_BRANCH p IFS=/ read -a repo <<< $TRAVIS_REPO_SLUG mv ../${repo[1]} plugins/flyvemdm +# patch settings +PATCH_ARGS="-p1 -N --batch" + # patch Fusion Inventory when needed cd plugins/fusioninventory -if [[ $FI_BRANCH == "master" ]] ; then patch -p1 --batch < ../flyvemdm/tests/patches/fusioninventory/fi-raise-max-version.patch; fi -if [[ $FI_BRANCH == "master" ]] ; then patch -p1 --batch < ../flyvemdm/tests/patches/fusioninventory/compat-glpi-9-3-2.diff; fi -if [[ $FI_BRANCH == "glpi9.3" ]] ; then patch -p1 --batch < ../flyvemdm/tests/patches/fusioninventory/compat-glpi-9-3-2.diff; fi +if [[ $FI_BRANCH == "master" ]] ; then patch $PATCH_ARGS < ../flyvemdm/tests/patches/fusioninventory/fi-raise-max-version.patch; fi +if [[ $FI_BRANCH == "master" ]] ; then patch $PATCH_ARGS < ../flyvemdm/tests/patches/fusioninventory/compat-glpi-9-3-2.diff; fi +if [[ $FI_BRANCH == "glpi9.3" ]] ; then patch $PATCH_ARGS < ../flyvemdm/tests/patches/fusioninventory/compat-glpi-9-3-2.diff; fi cd ../.. # patch GLPI when needed -# if [[ $GLPI_BRANCH == "9.2.1" ]] ; then patch -p1 --batch < plugins/flyvemdm/tests/patches/glpi/10f8dabfc5e20bb5a4e7d4ba4b93706871156a8a.diff; fi # prepare plugin to test cd plugins/flyvemdm -if [[ $GLPI_BRANCH == "master" ]] ; then patch -p1 --batch < tests/patches/allow-test-on-master-branch.patch; fi +if [[ $GLPI_BRANCH == "master" ]] ; then patch $PATCH_ARGS < tests/patches/allow-test-on-master-branch.patch; fi composer install --no-interaction From 74072ffe6b2dcd9628fdc86a7e4918dfd75dc167 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 19 Oct 2018 10:31:33 +0200 Subject: [PATCH 3/3] test(agent): enhance tests Signed-off-by: Thierry Bugier --- inc/agent.class.php | 4 ++ tests/src/Flyvemdm/Tests/CommonTestCase.php | 8 ++++ .../suite-integration/PluginFlyvemdmAgent.php | 39 +++++++++++++------ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/inc/agent.class.php b/inc/agent.class.php index 96509809..d18c2be5 100644 --- a/inc/agent.class.php +++ b/inc/agent.class.php @@ -393,10 +393,12 @@ public function canUpdateItem() { return parent::canUpdateItem(); } + // The user has a guest profile if (!$this->checkEntity(true)) { return false; } + // Only the account of the device can update return $_SESSION['glpiID'] == $this->fields[User::getForeignKeyField()]; } @@ -485,6 +487,8 @@ public function prepareInputForAdd($input) { public function prepareInputForUpdate($input) { $config = Config::getConfigurationValues('flyvemdm', ['guest_profiles_id']); if ($_SESSION['glpiactiveprofile']['id'] == $config['guest_profiles_id']) { + // a guest profile is the device itself. + // @see self::canUpdateItem() return $this->prepareInputForUpdateFromDevice($input); } diff --git a/tests/src/Flyvemdm/Tests/CommonTestCase.php b/tests/src/Flyvemdm/Tests/CommonTestCase.php index 4db4a73b..c3485ace 100644 --- a/tests/src/Flyvemdm/Tests/CommonTestCase.php +++ b/tests/src/Flyvemdm/Tests/CommonTestCase.php @@ -688,4 +688,12 @@ protected function asserLastMqttlog( } return 0; } + + protected function assertInvitationLogHasMessage(\PluginFlyvemdmInvitation $invitation, $message) { + $invitationId = $invitation->getID(); + $fk = \PluginFlyvemdmInvitation::getForeignKeyField(); + $invitationLog = new \PluginFlyvemdmInvitationLog(); + $found = $invitationLog->find("`$fk` = '$invitationId' AND `event` = '$message'"); + return count($found); + } } diff --git a/tests/suite-integration/PluginFlyvemdmAgent.php b/tests/suite-integration/PluginFlyvemdmAgent.php index 97120fe6..5b9ff730 100644 --- a/tests/suite-integration/PluginFlyvemdmAgent.php +++ b/tests/suite-integration/PluginFlyvemdmAgent.php @@ -127,30 +127,35 @@ protected function providerInvalidEnrollAgent() { 'invitationToken' => 'bad token', ], 'expected' => 'Invitation token invalid', + 'invitationlog' => false, ], 'without MDM type' => [ 'data' => [ 'mdmType' => null, ], 'expected' => 'MDM type missing', + 'invitationlog' => true, ], 'with bad MDM type' => [ 'data' => [ 'mdmType' => 'alien MDM', ], 'expected' => 'unknown MDM type', + 'invitationlog' => true, ], 'with bad version' => [ 'data' => [ 'version' => 'bad version', ], 'expected' => 'Bad agent version', + 'invitationlog' => true, ], 'with a too low version' => [ 'data' => [ 'version' => '1.9.0', ], 'expected' => 'The agent version is too low', + 'invitationlog' => true, ], 'without inventory' => [ 'data' => [ @@ -158,6 +163,7 @@ protected function providerInvalidEnrollAgent() { 'inventory' => '', ], 'expected' => 'Device inventory XML is mandatory', + 'invitationlog' => true, ], 'with invalid inventory' => [ 'data' => [ @@ -166,6 +172,7 @@ protected function providerInvalidEnrollAgent() { 'inventory' => $inventory, ], 'expected' => 'Inventory XML is not well formed', + 'invitationlog' => true, ], ]; } @@ -175,12 +182,17 @@ protected function providerInvalidEnrollAgent() { * @tags testInvalidEnrollAgent * @param array $data * @param string $expected + * @param boolean $invitationlog Is an invitation log entry expected ? */ - public function testInvalidEnrollAgent(array $data, $expected) { + public function testInvalidEnrollAgent(array $data, $expected, $invitationlog) { $dbUtils = new \DbUtils; $invitationlogTable = \PluginFlyvemdmInvitationlog::getTable(); $expectedLogCount = $dbUtils->countElementsInTable($invitationlogTable); list($user, $serial, $guestEmail, $invitation) = $this->createUserInvitation(\User::getForeignKeyField()); + + //Check there is no event for the invitation + $this->integer($this->assertInvitationLogHasMessage($invitation, $expected))->isEqualTo(0); + $invitationToken = (isset($data['invitationToken'])) ? $data['invitationToken'] : $invitation->getField('invitation_token'); $serial = (key_exists('serial', $data)) ? $data['serial'] : $serial; $mdmType = (key_exists('mdmType', $data)) ? $data['mdmType'] : 'android'; @@ -194,15 +206,10 @@ public function testInvalidEnrollAgent(array $data, $expected) { ->isTrue(json_encode($_SESSION['MESSAGE_AFTER_REDIRECT'], JSON_PRETTY_PRINT)); $this->array($_SESSION['MESSAGE_AFTER_REDIRECT'][ERROR])->contains($expected); - // Check registered log - // previous enrolment could generate a new log - $invitationLog = new \PluginFlyvemdmInvitationlog(); - $fk = \PluginFlyvemdmInvitation::getForeignKeyField(); - $inviationId = $invitation->getID(); - $expectedLogCount += count($invitationLog->find("`$fk` = '$inviationId'")); - - $total = $dbUtils->countElementsInTable($invitationlogTable); - $this->integer($total)->isEqualTo($expectedLogCount); + // Check that an event was created for the invitation + if ($invitationlog) { + $this->integer($this->assertInvitationLogHasMessage($invitation, $expected))->isGreaterThan(0); + } } /** @@ -382,7 +389,7 @@ public function testUnenrollAgent() { $this->boolean($agent->isNewItem()) ->isFalse(json_encode($_SESSION['MESSAGE_AFTER_REDIRECT'], JSON_PRETTY_PRINT)); - sleep(2); + $this->login('glpi', 'glpi'); // Find the last existing ID of logged MQTT messages $log = new \PluginFlyvemdmMqttlog(); @@ -446,6 +453,8 @@ public function testDeviceOnlineChange() { $this->boolean($agent->isNewItem()) ->isFalse(json_encode($_SESSION['MESSAGE_AFTER_REDIRECT'], JSON_PRETTY_PRINT)); + $this->login('glpi', 'glpi'); + $this->deviceOnlineStatus($agent, 'true', 1); $this->deviceOnlineStatus($agent, 'false', 0); @@ -581,6 +590,8 @@ public function testPingRequest() { // Get enrolment data to enable the agent's MQTT account $this->boolean($agent->getFromDB($agent->getID()))->isTrue(); + $this->login('glpi', 'glpi'); + // Find the last existing ID of logged MQTT messages $log = new \PluginFlyvemdmMqttlog(); $lastLogId = \PluginFlyvemdmCommon::getMax($log, '', 'id'); @@ -616,6 +627,8 @@ public function testGeolocateRequest() { // Get enrolment data to enable the agent's MQTT account $this->boolean($agent->getFromDB($agent->getID()))->isTrue(); + $this->login('glpi', 'glpi'); + // Find the last existing ID of logged MQTT messages $log = new \PluginFlyvemdmMqttlog(); $lastLogId = \PluginFlyvemdmCommon::getMax($log, '', 'id'); @@ -647,6 +660,8 @@ public function testInventoryRequest() { $this->boolean($agent->isNewItem()) ->isFalse(json_encode($_SESSION['MESSAGE_AFTER_REDIRECT'], JSON_PRETTY_PRINT)); + $this->login('glpi', 'glpi'); + // Get enrolment data to enable the agent's MQTT account $this->boolean($agent->getFromDB($agent->getID()))->isTrue(); @@ -684,6 +699,8 @@ public function testLockAndWipe() { $this->boolean($agent->isNewItem()) ->isFalse(json_encode($_SESSION['MESSAGE_AFTER_REDIRECT'], JSON_PRETTY_PRINT)); + $this->login('glpi', 'glpi'); + // Test lock and wipe are unset after enrollment $this->integer((int) $agent->getField('lock'))->isEqualTo(0); $this->integer((int) $agent->getField('wipe'))->isEqualTo(0);