diff --git a/CHANGELOG.md b/CHANGELOG.md index 882b794..52a7271 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- Encrypt stored auth credentials, parameterize device queries, escape XML/error output - Sanitize invalid characters from SCCM ## [2.6.0] - 2026-05-26 diff --git a/inc/config.class.php b/inc/config.class.php index 4439ba7..f98b7e2 100644 --- a/inc/config.class.php +++ b/inc/config.class.php @@ -148,6 +148,12 @@ public function prepareInputForUpdate($input): array unset($input["sccmdb_password"]); } + if (isset($input["auth_info"]) && !empty($input["auth_info"])) { + $input["auth_info"] = (new GLPIKey())->encrypt($input["auth_info"]); + } else { + unset($input["auth_info"]); + } + if (array_key_exists('inventory_server_url', $input) && !empty($input['inventory_server_url'])) { $input['inventory_server_url'] = trim((string) $input['inventory_server_url'], '/ '); } @@ -163,15 +169,17 @@ public function showForm($ID, array $options = []): bool $this->initForm($ID, $options); $password = (new GLPIKey())->decrypt($this->fields['sccmdb_password'] ?? ''); + $auth_info = (new GLPIKey())->decrypt($this->fields['auth_info'] ?? ''); $url = ($this->fields['inventory_server_url'] ?: __('Example:') . ' ' . $CFG_GLPI['url_base']) . '/front/inventory.php'; TemplateRenderer::getInstance()->display( '@sccm/config.html.twig', [ - 'item' => $this, - 'params' => $options, - 'password_display' => $password, - 'url' => $url, + 'item' => $this, + 'params' => $options, + 'password_display' => $password, + 'auth_info_display' => $auth_info, + 'url' => $url, ], ); @@ -216,7 +224,7 @@ public static function install(Migration $migration): bool `sccm_collection_name` VARCHAR(255) NULL, `inventory_server_url` VARCHAR(255) NULL, `active_sync` tinyint NOT NULL DEFAULT '0', - `verify_ssl_cert` tinyint NOT NULL DEFAULT '0', + `verify_ssl_cert` tinyint NOT NULL DEFAULT '1', `use_auth_ntlm` tinyint NOT NULL DEFAULT '0', `unrestricted_auth` tinyint NOT NULL DEFAULT '0', `use_auth_info` tinyint NOT NULL DEFAULT '0', @@ -255,7 +263,7 @@ public static function install(Migration $migration): bool } if (!$DB->fieldExists($table, 'verify_ssl_cert')) { - $migration->addField($table, "verify_ssl_cert", "tinyint NOT NULL default '0'"); + $migration->addField($table, "verify_ssl_cert", "tinyint NOT NULL default '1'"); $migration->migrationOneTable($table); } @@ -298,6 +306,25 @@ public static function install(Migration $migration): bool $migration->migrationOneTable($table); } + // Encrypt auth_info — iterate ALL configs + if (!$DB->fieldExists($table, 'is_auth_info_encrypted')) { + $key = new GLPIKey(); + foreach ($DB->request(['FROM' => $table]) as $config_data) { + if (!empty($config_data['auth_info'])) { + $migration->addPostQuery( + $DB->buildUpdate( + $table, + ['auth_info' => $key->encrypt($config_data['auth_info'])], + ['id' => $config_data['id']], + ), + ); + } + } + + $migration->addField($table, "is_auth_info_encrypted", "tinyint NOT NULL default '1'"); + $migration->migrationOneTable($table); + } + if (!$DB->fieldExists($table, 'sccm_collection_name')) { $migration->addField($table, 'sccm_collection_name', 'string', ['after' => 'sccmdb_password', 'value' => '']); $migration->migrationOneTable($table); diff --git a/inc/sccm.class.php b/inc/sccm.class.php index 83611c6..3a157d8 100644 --- a/inc/sccm.class.php +++ b/inc/sccm.class.php @@ -84,11 +84,13 @@ public function getDevices(int $config_id, $where = 0, $limit = 99999999): void $query = self::getcomputerQuery($collection_name); + $params = []; if ($where != 0) { - $query .= " WHERE csd.MachineID = '" . $where . "'"; + $query .= " WHERE csd.MachineID = ?"; + $params[] = $where; } - $result = $sccm_db->exec_query($query); + $result = $sccm_db->exec_query($query, $params); $i = 0; $this->devices = []; @@ -114,9 +116,9 @@ public function getDatas(PluginSccmSccmdb $sccm_db, $type, $deviceid, $limit = 9 $query = "SELECT " . implode(',', $fields) . "\n"; $query .= " FROM " . $table . "\n"; - $query .= " WHERE MachineID = '" . $deviceid . "'" . "\n"; + $query .= " WHERE MachineID = ?" . "\n"; - $result = $sccm_db->exec_query($query); + $result = $sccm_db->exec_query($query, [$deviceid]); $data = []; $i = 0; @@ -141,9 +143,9 @@ public function getNetwork(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 999999 INNER JOIN v_R_System VrS ON VrS.ResourceID=NeDa.MachineID INNER JOIN v_GS_NETWORK_ADAPTER net ON net.ResourceID=NeDa.MachineID AND NeDa.ServiceName00=net.ServiceName0 WHERE MACAddress00 is not null - AND NeDa.MachineID = '" . $deviceid . "'"; + AND NeDa.MachineID = ?"; - $result = $sccm_db->exec_query($query); + $result = $sccm_db->exec_query($query, [$deviceid]); $data = []; $i = 0; @@ -163,7 +165,7 @@ public function getSoftware(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 99999 ArPd_64.Publisher0 as \"ArPd-Publisher\" FROM v_GS_ADD_REMOVE_PROGRAMS_64 ArPd_64 INNER JOIN v_R_System VrS on VrS.ResourceID=ArPd_64.ResourceID - WHERE ArPd_64.ResourceID = {$deviceid} + WHERE ArPd_64.ResourceID = ? AND (ArPd_64.DisplayName0 is not null and ArPd_64.DisplayName0 <> '') UNION SELECT ArPd.DisplayName0 as \"ArPd-DisplayName\", @@ -172,10 +174,10 @@ public function getSoftware(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 99999 ArPd.Publisher0 as \"ArPd-Publisher\" FROM v_GS_ADD_REMOVE_PROGRAMS ArPd INNER JOIN v_R_System VrS on VrS.ResourceID=ArPd.ResourceID - WHERE ArPd.ResourceID = {$deviceid} + WHERE ArPd.ResourceID = ? AND (ArPd.DisplayName0 is not null and ArPd.DisplayName0 <> '')"; - $result = $sccm_db->exec_query($query); + $result = $sccm_db->exec_query($query, [$deviceid, $deviceid]); $data = []; $i = 0; @@ -202,10 +204,10 @@ public function getMemories(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 99999 GroupID as \"Mem-NumSlots\", '' as \"Mem-SerialNumber\" FROM v_GS_PHYSICAL_MEMORY - WHERE ResourceID = '" . $deviceid . "' + WHERE ResourceID = ? ORDER BY \"Mem-NumSlots\""; - $result = $sccm_db->exec_query($query); + $result = $sccm_db->exec_query($query, [$deviceid]); $data = []; $i = 0; @@ -228,10 +230,10 @@ public function getVideos(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 9999999 GroupID as \"Vid-PciSlot\" FROM v_GS_VIDEO_CONTROLLER WHERE VideoProcessor0 is not null - AND ResourceID = '" . $deviceid . "' + AND ResourceID = ? ORDER BY GroupID"; - $result = $sccm_db->exec_query($query); + $result = $sccm_db->exec_query($query, [$deviceid]); $data = []; $i = 0; @@ -251,9 +253,9 @@ public function getSounds(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 9999999 Manufacturer0 as \"Snd-Manufacturer\", Name0 as \"Snd-Name\" FROM v_GS_SOUND_DEVICE - WHERE ResourceID = '" . $deviceid . "'"; + WHERE ResourceID = ?"; - $result = $sccm_db->exec_query($query); + $result = $sccm_db->exec_query($query, [$deviceid]); $data = []; $i = 0; @@ -282,9 +284,9 @@ public function getStorages(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 99999 INNER JOIN v_gs_Disk as gdi on gdi.ResourceID = gld.ResourceID LEFT JOIN Motherboard_DATA as md on gld.ResourceID = md.MachineID WHERE gld.GroupID = gdi.GroupID - AND gld.ResourceID = '" . $deviceid . "'"; + AND gld.ResourceID = ?"; - $result = $sccm_db->exec_query($query); + $result = $sccm_db->exec_query($query, [$deviceid]); $data = []; $i = 0; @@ -307,9 +309,9 @@ public function getMedias(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 9999999 SCSITargetID0 as \"Med-SCSITargetId\", MediaType0 as \"Med-Type\" FROM v_GS_CDROM - WHERE ResourceID = '" . $deviceid . "'"; + WHERE ResourceID = ?"; - $result = $sccm_db->exec_query($query); + $result = $sccm_db->exec_query($query, [$deviceid]); $data = []; $i = 0; @@ -433,6 +435,11 @@ public static function executeCollect($task): int } foreach ($PluginSccmSccm->devices as $device_values) { + if (!ctype_digit((string) ($device_values['CSD-MachineID'] ?? ''))) { + Toolbox::logInFile('sccm', sprintf('[Config %s] Invalid device id, skipping: ', $config_id) . ($device_values['CSD-MachineID'] ?? '') . "\n", true); + continue; + } + $PluginSccmSccmxml = new PluginSccmSccmxml($device_values); $PluginSccmSccmxml->setAccessLog(); @@ -569,6 +576,11 @@ public static function executePush(CronTask $task): int $result = $PluginSccmSccmdb->exec_query($query); while ($tab = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC)) { + if (!ctype_digit((string) ($tab['CSD-MachineID'] ?? ''))) { + Toolbox::logInFile('sccm', sprintf('[Config %s] Invalid device id, skipping: ', $config_id) . ($tab['CSD-MachineID'] ?? '') . "\n", true); + continue; + } + $REP_XML = realpath(GLPI_PLUGIN_DOC_DIR . '/sccm/xml/' . $config_id . '/' . $tab['CSD-MachineID'] . '.ocs'); if ($REP_XML === '0') { @@ -599,7 +611,7 @@ public static function executePush(CronTask $task): int } if ($config->getField('use_auth_info') == "1") { - curl_setopt($ch, CURLOPT_USERPWD, $config->getField('auth_info')); + curl_setopt($ch, CURLOPT_USERPWD, (new GLPIKey())->decrypt($config->getField('auth_info') ?? '')); } $url = ($config->getField('inventory_server_url') ?: $CFG_GLPI['url_base']) . '/front/inventory.php'; diff --git a/inc/sccmdb.class.php b/inc/sccmdb.class.php index f6b4dec..f57cded 100644 --- a/inc/sccmdb.class.php +++ b/inc/sccmdb.class.php @@ -73,9 +73,9 @@ public function disconnect(): void sqlsrv_close($this->dbconn); } - public function exec_query($query) + public function exec_query($query, array $params = []) { - $result = sqlsrv_query($this->dbconn, $query); + $result = sqlsrv_query($this->dbconn, $query, $params); if ($result == false) { throw new BadRequestHttpException('Query error: ' . print_r(sqlsrv_errors(), true)); } @@ -89,7 +89,7 @@ public function FormatErrors(array $errors): void $state = "SQLSTATE: " . $error['SQLSTATE']; $code = "Code: " . $error['code']; $message = "Message: " . $error['message']; - echo $state . "
" . $code . "
" . $message . "
"; + echo htmlspecialchars($state) . "
" . htmlspecialchars($code) . "
" . htmlspecialchars($message) . "
"; Toolbox::logInFile("sccm", $state . PHP_EOL . $code . PHP_EOL . $message . PHP_EOL); } } diff --git a/inc/sccmxml.class.php b/inc/sccmxml.class.php index 9e82abc..7a03547 100644 --- a/inc/sccmxml.class.php +++ b/inc/sccmxml.class.php @@ -56,12 +56,12 @@ public function __construct(public array $data) {$this->agentbuildnumber} - {$this->device_id} INVENTORY XML; $this->sxml = new SimpleXMLElement($SXML); + $this->sxml->addChild('DEVICEID', $this->device_id); } public function setAccessLog(): void diff --git a/rector.php b/rector.php index a58505d..9ddbe93 100644 --- a/rector.php +++ b/rector.php @@ -29,29 +29,27 @@ * ------------------------------------------------------------------------- */ +use Rector\Configuration\RectorConfigBuilder; + require_once __DIR__ . '/../../src/Plugin.php'; -use Rector\Caching\ValueObject\Storage\FileCacheStorage; -use Rector\Config\RectorConfig; -use Rector\ValueObject\PhpVersion; +$baseline_file = __DIR__ . '/../../PluginsRector.php'; +if (!file_exists($baseline_file)) { + throw new RuntimeException( + sprintf( + 'Unable to find "%s". Running rector on a plugin requires a GLPI development checkout that ships PluginsRector.php.', + $baseline_file, + ), + ); +} + +$baseline = require $baseline_file; + +/** @var RectorConfigBuilder $config */ +$config = $baseline([ + __DIR__ . '/front', + __DIR__ . '/inc', + __DIR__ . '/tests', +]); -return RectorConfig::configure() - ->withPaths([ - __DIR__ . '/front', - __DIR__ . '/inc', - ]) - ->withPhpVersion(PhpVersion::PHP_82) - ->withCache( - cacheDirectory: __DIR__ . '/var/rector', - cacheClass: FileCacheStorage::class, - ) - ->withRootFiles() - ->withParallel(timeoutSeconds: 300) - ->withImportNames(removeUnusedImports: true) - ->withPreparedSets( - deadCode: true, - codeQuality: true, - codingStyle: true, - ) - ->withPhpSets(php82: true) // apply PHP sets up to PHP 8.2 -; +return $config; diff --git a/setup.php b/setup.php index b517345..a0b2b05 100644 --- a/setup.php +++ b/setup.php @@ -1,5 +1,36 @@ . + * ------------------------------------------------------------------------- + * @author François Legastelois + * @copyright Copyright (C) 2014-2023 by SCCM plugin team. + * @license GPLv3 https://www.gnu.org/licenses/gpl-3.0.html + * @link https://github.com/pluginsGLPI/sccm + * ------------------------------------------------------------------------- + */ + +declare(strict_types=1); + /** * ------------------------------------------------------------------------- * SCCM plugin for GLPI @@ -51,7 +82,10 @@ function plugin_init_sccm() } // Encryption - $PLUGIN_HOOKS[Hooks::SECURED_FIELDS]['sccm'] = ['glpi_plugin_sccm_configs.sccmdb_password']; + $PLUGIN_HOOKS[Hooks::SECURED_FIELDS]['sccm'] = [ + 'glpi_plugin_sccm_configs.sccmdb_password', + 'glpi_plugin_sccm_configs.auth_info', + ]; } /** diff --git a/templates/config.html.twig b/templates/config.html.twig index b415b0b..fbdb5a6 100644 --- a/templates/config.html.twig +++ b/templates/config.html.twig @@ -107,10 +107,11 @@ __('Use specific authentication information', 'sccm') ) }} - {{ fields.textField( + {{ fields.passwordField( 'auth_info', - item.fields['auth_info'], - __('Value for specific authentication', 'sccm') + auth_info_display, + __('Value for specific authentication', 'sccm'), + {'autocomplete': 'off'} ) }} {{ fields.checkboxField( diff --git a/tests/PluginSccmSccmTest.php b/tests/PluginSccmSccmTest.php index 2c10746..c985ce5 100644 --- a/tests/PluginSccmSccmTest.php +++ b/tests/PluginSccmSccmTest.php @@ -36,9 +36,9 @@ class PluginSccmSccmTest extends GLPITestCase { private function callSanitizeRow(array $row): array { - $sccm = (new \ReflectionClass(PluginSccmSccm::class))->newInstanceWithoutConstructor(); + $sccm = (new ReflectionClass(PluginSccmSccm::class))->newInstanceWithoutConstructor(); - return (new \ReflectionMethod(PluginSccmSccm::class, 'sanitizeRow'))->invoke($sccm, $row); + return (new ReflectionMethod(PluginSccmSccm::class, 'sanitizeRow'))->invoke($sccm, $row); } public function testSanitizeRowPreservesCleanString(): void diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 3744113..355023a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -35,5 +35,5 @@ require dirname(__DIR__) . '/vendor/autoload.php'; if (!Plugin::isPluginActive($current_plugin_folder)) { - throw new RuntimeException("Plugin $current_plugin_folder is not active in the test database"); + throw new RuntimeException(sprintf('Plugin %s is not active in the test database', $current_plugin_folder)); }