From d7e1dd6f79a5e6b7d252b73141a02187a61c748a Mon Sep 17 00:00:00 2001 From: Michael Rodrigues Date: Mon, 13 Jul 2026 12:13:18 -0300 Subject: [PATCH 1/3] Update Update to better manage access to the dashboards. --- front/itemright.form.php | 125 ++++++++++++ inc/dashboard.class.php | 61 +++++- inc/itemright.class.php | 405 +++++++++++++++++++++++++++++++++++++++ setup.php | 5 +- 4 files changed, 589 insertions(+), 7 deletions(-) create mode 100644 front/itemright.form.php create mode 100644 inc/itemright.class.php diff --git a/front/itemright.form.php b/front/itemright.form.php new file mode 100644 index 0000000..ede1891 --- /dev/null +++ b/front/itemright.form.php @@ -0,0 +1,125 @@ +. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2018-2023 by Metabase plugin team. + * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + * @link https://github.com/pluginsGLPI/metabase + * ------------------------------------------------------------------------- + * + * CUSTOM FORK NOTICE: added to handle per Group / per User dashboard rights. + * ------------------------------------------------------------------------- + */ + +function plugin_metabase_check_itemright_access($itemtype) +{ + if (!in_array($itemtype, PluginMetabaseItemright::SUPPORTED_ITEMTYPES, true)) { + Session::addMessageAfterRedirect( + __s('Invalid request.', 'metabase'), + false, + ERROR, + ); + Html::back(); + } + + if ($itemtype === Group::class) { + Session::checkRight('group', UPDATE); + } else { + Session::checkRight('user', UPDATE); + } +} + +if (isset($_REQUEST['update'])) { + if ( + !array_key_exists('itemtype', $_REQUEST) + || empty($_REQUEST['itemtype']) + || !array_key_exists('items_id', $_REQUEST) + || empty($_REQUEST['items_id']) + || !array_key_exists('dashboard', $_REQUEST) + || !is_array($_REQUEST['dashboard']) + ) { + Session::addMessageAfterRedirect( + __s('Invalid request.', 'metabase'), + false, + ERROR, + ); + Html::back(); + } + + plugin_metabase_check_itemright_access($_REQUEST['itemtype']); + + $viewableDashboardsUuids = []; + foreach ($_REQUEST['dashboard'] as $dashboardUuid => $rights) { + PluginMetabaseItemright::setDashboardRightsForItem( + $_REQUEST['itemtype'], + $_REQUEST['items_id'], + $dashboardUuid, + $rights, + ); + + if (($rights & READ) !== 0) { + $viewableDashboardsUuids[] = $dashboardUuid; + } + } + + if (!empty($viewableDashboardsUuids)) { + $apiclient = new PluginMetabaseAPIClient(); + $apiclient->enableDashboardsEmbeddedDisplay($viewableDashboardsUuids); + } +} elseif (isset($_REQUEST['set_rights_to_all'])) { + if ( + !array_key_exists('itemtype', $_REQUEST) + || empty($_REQUEST['itemtype']) + || !array_key_exists('items_id', $_REQUEST) + || empty($_REQUEST['items_id']) + ) { + Session::addMessageAfterRedirect( + __s('Invalid request.', 'metabase'), + false, + ERROR, + ); + Html::back(); + } + + plugin_metabase_check_itemright_access($_REQUEST['itemtype']); + + $apiclient = new PluginMetabaseAPIClient(); + + $viewableDashboardsUuids = []; + foreach ($apiclient->getDashboards() as $dashboard) { + PluginMetabaseItemright::setDashboardRightsForItem( + $_REQUEST['itemtype'], + $_REQUEST['items_id'], + $dashboard['id'], + $_REQUEST['set_rights_to_all'], + ); + + $viewableDashboardsUuids[] = $dashboard['id']; + } + + if ((int) $_REQUEST['set_rights_to_all'] !== 0) { + $apiclient->enableDashboardsEmbeddedDisplay($viewableDashboardsUuids); + } +} + +Html::back(); diff --git a/inc/dashboard.class.php b/inc/dashboard.class.php index 1a90775..d9fca74 100644 --- a/inc/dashboard.class.php +++ b/inc/dashboard.class.php @@ -43,6 +43,58 @@ public static function getTypeName($nb = 0) return __s('Metabase dashboard', 'metabase'); } + /** + * Check if the currently logged-in user is able to view at least one + * dashboard, combining profile, group and user based rights. + * + * CUSTOM FORK: rights are additive (OR) across profile / groups / user. + * + * @return boolean + */ + public static function canCurrentUserViewDashboards(): bool + { + if (PluginMetabaseProfileright::canProfileViewDashboards($_SESSION['glpiactiveprofile']['id'])) { + return true; + } + + if (PluginMetabaseItemright::canItemViewDashboards(User::class, Session::getLoginUserID())) { + return true; + } + + if (PluginMetabaseItemright::canGroupsViewDashboards($_SESSION['glpigroups'] ?? [])) { + return true; + } + + return false; + } + + /** + * Check if the currently logged-in user is able to view the given + * dashboard, combining profile, group and user based rights. + * + * CUSTOM FORK: rights are additive (OR) across profile / groups / user. + * + * @param integer $dashboardUuid + * + * @return boolean + */ + public static function canCurrentUserViewDashboard($dashboardUuid): bool + { + if (PluginMetabaseProfileright::canProfileViewDashboard($_SESSION['glpiactiveprofile']['id'], $dashboardUuid)) { + return true; + } + + if (PluginMetabaseItemright::canItemViewDashboard(User::class, Session::getLoginUserID(), $dashboardUuid)) { + return true; + } + + if (PluginMetabaseItemright::canGroupsViewDashboard($_SESSION['glpigroups'] ?? [], $dashboardUuid)) { + return true; + } + + return false; + } + /** * {@inheritDoc} * @see CommonGLPI::getTabNameForItem() @@ -51,7 +103,7 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { switch ($item->getType()) { case 'Central': - if (PluginMetabaseProfileright::canProfileViewDashboards($_SESSION['glpiactiveprofile']['id'])) { + if (self::canCurrentUserViewDashboards()) { return self::createTabEntry(self::getTypeName(), 0, $item::getType(), PluginMetabaseConfig::getIcon()); } @@ -69,7 +121,7 @@ public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $ { switch (get_class($item)) { case Central::class: - if (PluginMetabaseProfileright::canProfileViewDashboards($_SESSION['glpiactiveprofile']['id'])) { + if (self::canCurrentUserViewDashboards()) { self::showForCentral($item, $withtemplate); } @@ -99,10 +151,7 @@ public static function showForCentral(Central $item, $withtemplate = 0, $is_help $dashboards, function ($dashboard) { $isEmbeddingEnabled = $dashboard['enable_embedding']; - $canView = PluginMetabaseProfileright::canProfileViewDashboard( - $_SESSION['glpiactiveprofile']['id'], - $dashboard['id'], - ); + $canView = self::canCurrentUserViewDashboard($dashboard['id']); return $isEmbeddingEnabled && $canView; }, diff --git a/inc/itemright.class.php b/inc/itemright.class.php new file mode 100644 index 0000000..72a882a --- /dev/null +++ b/inc/itemright.class.php @@ -0,0 +1,405 @@ +. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2018-2023 by Metabase plugin team. + * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + * @link https://github.com/pluginsGLPI/metabase + * ------------------------------------------------------------------------- + * + * CUSTOM FORK NOTICE + * ------------------------------------------------------------------------- + * This class was added in a local fork to allow defining dashboard access + * rights per Group and per User, in addition to the native per Profile + * rights handled by PluginMetabaseProfileright. + * ------------------------------------------------------------------------- + */ + +class PluginMetabaseItemright extends CommonDBTM +{ + /** + * Itemtypes supported by this "generic" right holder. + */ + public const SUPPORTED_ITEMTYPES = [Group::class, User::class]; + + /** + * {@inheritDoc} + * @see CommonGLPI::getTypeName() + */ + public static function getTypeName($nb = 0) + { + return __s('Metabase', 'metabase'); + } + + /** + * Returns the GLPI right (and value) required to manage metabase + * dashboard rights for the given itemtype. + * + * @param string $itemtype Group::class or User::class + * + * @return array{0: string, 1: int} [rightname, right value] + */ + private static function getRequiredRight(string $itemtype): array + { + return match ($itemtype) { + Group::class => ['group', UPDATE], + User::class => ['user', UPDATE], + default => ['profile', UPDATE], + }; + } + + /** + * {@inheritDoc} + * @see CommonGLPI::getTabNameForItem() + */ + public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) + { + $itemtype = $item::getType(); + + if (!in_array($itemtype, self::SUPPORTED_ITEMTYPES, true)) { + return ''; + } + + [$rightname, $rightvalue] = self::getRequiredRight($itemtype); + if (Session::haveRight($rightname, $rightvalue)) { + return self::createTabEntry(self::getTypeName(), 0, $itemtype, PluginMetabaseConfig::getIcon()); + } + + return ''; + } + + /** + * {@inheritDoc} + * @see CommonGLPI::displayTabContentForItem() + */ + public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) + { + $itemtype = $item::getType(); + + if (!in_array($itemtype, self::SUPPORTED_ITEMTYPES, true)) { + return true; + } + + [$rightname, $rightvalue] = self::getRequiredRight($itemtype); + if (Session::haveRight($rightname, $rightvalue)) { + $itemright = new self(); + $itemright->showRightsForm($itemtype, $item->fields['id']); + } + + return true; + } + + /** + * Display item (group/user) rights form. + * + * @param string $itemtype Group::class or User::class + * @param integer $itemsId Group or User id + * @param array $options + * + * @return bool + */ + public function showRightsForm($itemtype, $itemsId, $options = []) + { + if (!in_array($itemtype, self::SUPPORTED_ITEMTYPES, true)) { + return false; + } + + [$rightname, $rightvalue] = self::getRequiredRight($itemtype); + if (!Session::haveRight($rightname, $rightvalue)) { + return false; + } + + $apiclient = new PluginMetabaseAPIClient(); + $dashboards = $apiclient->getDashboards(); + + if (!$dashboards) { + echo '
' . __s('No dashboards found in Metabase.', 'metabase') . '
'; + return false; + } + + echo '
'; + echo '
'; + echo ''; + + echo ''; + + Plugin::doHook('pre_item_form', ['item' => $this, 'options' => &$options]); + + echo ''; + + echo ''; + echo ''; + + echo ''; + echo ''; + echo ''; + + foreach ($dashboards as $dashboard) { + echo ''; + echo ''; + echo ''; + echo ''; + } + + echo ''; + echo ''; + echo ''; + + echo '
' . self::getTypeName() . '
' . __s('Rights management', 'metabase') . '
'; + echo ''; + echo '   '; + echo ''; + echo '
' . $dashboard['name'] . ''; + Profile::dropdownRight( + sprintf('dashboard[%d]', $dashboard['id']), + [ + 'value' => self::getItemRightForDashboard($itemtype, $itemsId, $dashboard['id']), + 'nonone' => 0, + 'noread' => 0, + 'nowrite' => 1, + ], + ); + echo '
'; + echo Html::submit(_sx('button', 'Save'), [ + 'name' => 'update', + 'icon' => 'ti ti-device-floppy', + 'class' => 'btn btn-primary', + ]); + echo '
'; + echo '
'; + + Html::closeForm(); + + return true; + } + + /** + * Check if any group from the given list is able to view at least one dashboard. + * + * @param int[] $groupIds + * + * @return boolean + */ + public static function canGroupsViewDashboards(array $groupIds): bool + { + foreach ($groupIds as $groupId) { + if (self::canItemViewDashboards(Group::class, $groupId)) { + return true; + } + } + + return false; + } + + /** + * Check if any group from the given list is able to view the given dashboard. + * + * @param int[] $groupIds + * @param integer $dashboardUuid + * + * @return boolean + */ + public static function canGroupsViewDashboard(array $groupIds, $dashboardUuid): bool + { + foreach ($groupIds as $groupId) { + if (self::canItemViewDashboard(Group::class, $groupId, $dashboardUuid)) { + return true; + } + } + + return false; + } + + /** + * Check if item (group/user) is able to view at least one dashboard. + * + * @param string $itemtype + * @param integer $itemsId + * + * @return boolean + */ + public static function canItemViewDashboards($itemtype, $itemsId): bool + { + /** @var DBmysql $DB */ + global $DB; + + if (empty($itemsId)) { + return false; + } + + $iterator = $DB->request( + [ + 'FROM' => self::getTable(), + 'WHERE' => [ + 'itemtype' => $itemtype, + 'items_id' => $itemsId, + ], + ], + ); + + foreach ($iterator as $right) { + if (($right['rights'] & READ) !== 0) { + return true; + } + } + + return false; + } + + /** + * Check if item (group/user) is able to view given dashboard. + * + * @param string $itemtype + * @param integer $itemsId + * @param integer $dashboardUuid + * + * @return boolean + */ + public static function canItemViewDashboard($itemtype, $itemsId, $dashboardUuid): bool + { + return (self::getItemRightForDashboard($itemtype, $itemsId, $dashboardUuid) & READ) !== 0; + } + + /** + * Returns item (group/user) rights for given dashboard. + * + * @param string $itemtype + * @param integer $itemsId + * @param integer $dashboardUuid + * + * @return integer + */ + private static function getItemRightForDashboard($itemtype, $itemsId, $dashboardUuid) + { + if (empty($itemsId)) { + return 0; + } + + $rightCriteria = [ + 'itemtype' => $itemtype, + 'items_id' => $itemsId, + 'dashboard_uuid' => $dashboardUuid, + ]; + + $itemright = new self(); + if ($itemright->getFromDBByCrit($rightCriteria)) { + return $itemright->fields['rights']; + } + + return 0; + } + + /** + * Defines item (group/user) rights for dashboard. + * + * @param string $itemtype + * @param integer $itemsId + * @param integer $dashboardUuid + * @param integer $rights + * + * @return void + */ + public static function setDashboardRightsForItem($itemtype, $itemsId, $dashboardUuid, $rights) + { + $itemright = new self(); + + $rightsExists = $itemright->getFromDBByCrit( + [ + 'itemtype' => $itemtype, + 'items_id' => $itemsId, + 'dashboard_uuid' => $dashboardUuid, + ], + ); + + if ($rightsExists) { + $itemright->update( + [ + 'id' => $itemright->fields['id'], + 'rights' => $rights, + ], + ); + } else { + $itemright->add( + [ + 'itemtype' => $itemtype, + 'items_id' => $itemsId, + 'dashboard_uuid' => $dashboardUuid, + 'rights' => $rights, + ], + ); + } + } + + /** + * Install itemrights database. + * + * @param Migration $migration + * + * @return void + */ + public static function install(Migration $migration) + { + /** @var DBmysql $DB */ + global $DB; + + $default_charset = DBConnection::getDefaultCharset(); + $default_collation = DBConnection::getDefaultCollation(); + $default_key_sign = DBConnection::getDefaultPrimaryKeySignOption(); + + $table = self::getTable(); + + if (!$DB->tableExists($table)) { + $migration->displayMessage("Installing $table"); + + $query = "CREATE TABLE IF NOT EXISTS `$table` ( + `id` int {$default_key_sign} NOT NULL AUTO_INCREMENT, + `itemtype` varchar(100) NOT NULL, + `items_id` int {$default_key_sign} NOT NULL, + `dashboard_uuid` int NOT NULL, + `rights` int NOT NULL, + PRIMARY KEY (`id`), + UNIQUE `itemtype_items_id_dashboard_uuid` (`itemtype`, `items_id`, `dashboard_uuid`) + ) ENGINE=InnoDB DEFAULT CHARSET={$default_charset} COLLATE={$default_collation} ROW_FORMAT=DYNAMIC;"; + $DB->doQuery($query); + } + } + + /** + * Uninstall itemrights database. + * + * @return void + */ + public static function uninstall() + { + /** @var DBmysql $DB */ + global $DB; + + $DB->doQuery('DROP TABLE IF EXISTS `' . self::getTable() . '`'); + } +} diff --git a/setup.php b/setup.php index ef8d9e9..320ec0b 100644 --- a/setup.php +++ b/setup.php @@ -78,7 +78,7 @@ function plugin_init_metabase() //display helpdesk menu if self-service and if is able to view at least one dashboard. if ( $_SESSION['glpiactiveprofile']['interface'] == 'helpdesk' - && PluginMetabaseProfileright::canProfileViewDashboards($_SESSION['glpiactiveprofile']['id']) + && PluginMetabaseDashboard::canCurrentUserViewDashboards() ) { $PLUGIN_HOOKS['helpdesk_menu_entry']['metabase'] = '/front/selfservice.php'; $PLUGIN_HOOKS['helpdesk_menu_entry_icon']['metabase'] = 'ti ti-chart-bar'; @@ -88,6 +88,9 @@ function plugin_init_metabase() // profile rights management Plugin::registerClass('PluginMetabaseProfileright', ['addtabon' => 'Profile']); + // CUSTOM FORK: group / user rights management + Plugin::registerClass('PluginMetabaseItemright', ['addtabon' => ['Group', 'User']]); + // css & js $PLUGIN_HOOKS['add_css']['metabase'] = 'metabase.css'; $PLUGIN_HOOKS['add_javascript']['metabase'] = 'metabase.js'; From 0e5679e383768b56bc4723434885c8486bf8f2ad Mon Sep 17 00:00:00 2001 From: Michael Rodrigues <143286293+MichaelRodriguesOficial@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:42:42 -0300 Subject: [PATCH 2/3] Update inc/dashboard.class.php Co-authored-by: Stanislas --- inc/dashboard.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/dashboard.class.php b/inc/dashboard.class.php index d9fca74..2336338 100644 --- a/inc/dashboard.class.php +++ b/inc/dashboard.class.php @@ -72,7 +72,7 @@ public static function canCurrentUserViewDashboards(): bool * Check if the currently logged-in user is able to view the given * dashboard, combining profile, group and user based rights. * - * CUSTOM FORK: rights are additive (OR) across profile / groups / user. + * rights are additive (OR) across profile / groups / user. * * @param integer $dashboardUuid * From 2adcd3400bfa9ff751b0e0b5f01c2adb518694c8 Mon Sep 17 00:00:00 2001 From: Michael Rodrigues <143286293+MichaelRodriguesOficial@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:00:53 -0300 Subject: [PATCH 3/3] Update inc/dashboard.class.php Co-authored-by: Stanislas --- inc/dashboard.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/dashboard.class.php b/inc/dashboard.class.php index 2336338..5995f17 100644 --- a/inc/dashboard.class.php +++ b/inc/dashboard.class.php @@ -78,7 +78,7 @@ public static function canCurrentUserViewDashboards(): bool * * @return boolean */ - public static function canCurrentUserViewDashboard($dashboardUuid): bool + public static function canCurrentUserViewDashboard(int $dashboardUuid): bool { if (PluginMetabaseProfileright::canProfileViewDashboard($_SESSION['glpiactiveprofile']['id'], $dashboardUuid)) { return true;