From dedb0d9722d6c47e9eed123e9072f76f48e80378 Mon Sep 17 00:00:00 2001 From: Elmahdi ABBASSI <108519266+emabassi-ext@users.noreply.github.com> Date: Fri, 29 Jul 2022 09:01:29 +0100 Subject: [PATCH 01/18] [SNYK] Sanitize and bind ACL host dependency queries (#11389) (#11394) * Sanitize and bind ACL host dependency queries * fix issues --- .../configObject/host_dependency/DB-Func.php | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/www/include/configuration/configObject/host_dependency/DB-Func.php b/www/include/configuration/configObject/host_dependency/DB-Func.php index a7b09cbeeaf..57209f88c74 100644 --- a/www/include/configuration/configObject/host_dependency/DB-Func.php +++ b/www/include/configuration/configObject/host_dependency/DB-Func.php @@ -123,11 +123,14 @@ function multipleHostDependencyInDB($dependencies = array(), $nbrDup = array()) "WHERE dependency_dep_id = " . $key; $dbResult = $pearDB->query($query); $fields["dep_serviceChilds"] = ""; + $statement = $pearDB->prepare("INSERT INTO dependency_serviceChild_relation " . + " VALUES (:max_dep_id, :service_id, :host_host_id)"); while ($service = $dbResult->fetch()) { - $query = "INSERT INTO dependency_serviceChild_relation VALUES ('" . - $maxId["MAX(dep_id)"] . "', '" . $service["service_service_id"] . "', '" . - $service["host_host_id"] . "')"; - $pearDB->query($query); + $statement->bindValue(':max_dep_id', (int)$maxId["MAX(dep_id)"], \PDO::PARAM_INT); + $statement->bindValue(':service_id', (int)$service["service_service_id"], \PDO::PARAM_INT); + $statement->bindValue(':host_host_id', (int)$service["host_host_id"], \PDO::PARAM_INT); + $statement->execute(); + $fields["dep_serviceChilds"] .= $service["host_host_id"] . '-' . $service["service_service_id"] . ","; } @@ -136,10 +139,12 @@ function multipleHostDependencyInDB($dependencies = array(), $nbrDup = array()) "WHERE dependency_dep_id = '" . $key . "'"; $dbResult = $pearDB->query($query); $fields["dep_hostParents"] = ""; + $statement = $pearDB->prepare("INSERT INTO dependency_hostParent_relation " . + "VALUES (:max_dep_id, :host_host_id)"); while ($host = $dbResult->fetch()) { - $query = "INSERT INTO dependency_hostParent_relation " . - "VALUES ('" . $maxId["MAX(dep_id)"] . "', '" . $host["host_host_id"] . "')"; - $pearDB->query($query); + $statement->bindValue(':max_dep_id', (int)$maxId["MAX(dep_id)"], \PDO::PARAM_INT); + $statement->bindValue(':host_host_id', (int)$host["host_host_id"], \PDO::PARAM_INT); + $statement->execute(); $fields["dep_hostParents"] .= $host["host_host_id"] . ","; } $fields["dep_hostParents"] = trim($fields["dep_hostParents"], ","); @@ -148,10 +153,12 @@ function multipleHostDependencyInDB($dependencies = array(), $nbrDup = array()) "WHERE dependency_dep_id = '" . $key . "'"; $dbResult = $pearDB->query($query); $fields["dep_hostChilds"] = ""; + $statement = $pearDB->prepare("INSERT INTO dependency_hostChild_relation " . + "VALUES (:max_dep_id, :host_host_id)"); while ($host = $dbResult->fetch()) { - $query = "INSERT INTO dependency_hostChild_relation " . - "VALUES ('" . $maxId["MAX(dep_id)"] . "', '" . $host["host_host_id"] . "')"; - $pearDB->query($query); + $statement->bindValue(':max_dep_id', (int)$maxId["MAX(dep_id)"], \PDO::PARAM_INT); + $statement->bindValue(':host_host_id', (int)$host["host_host_id"], \PDO::PARAM_INT); + $statement->execute(); $fields["dep_hostChilds"] .= $host["host_host_id"] . ","; } $fields["dep_hostChilds"] = trim($fields["dep_hostChilds"], ","); From 1756cf1f95cd5b06454f5542ec697ad035015d12 Mon Sep 17 00:00:00 2001 From: Elmahdi ABBASSI <108519266+emabassi-ext@users.noreply.github.com> Date: Fri, 29 Jul 2022 09:03:35 +0100 Subject: [PATCH 02/18] removed old variable userCrypted and the use of it (#11334) (#11352) Co-authored-by: jeremyjaouen <61694165+jeremyjaouen@users.noreply.github.com> --- www/class/centreonUser.class.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/www/class/centreonUser.class.php b/www/class/centreonUser.class.php index 2c61e80a732..398ed4733f2 100644 --- a/www/class/centreonUser.class.php +++ b/www/class/centreonUser.class.php @@ -56,7 +56,6 @@ class CentreonUser public $groupListStr; public $access; public $log; - public $userCrypted; protected $token; public $default_page; private $showDeprecatedPages; @@ -109,7 +108,6 @@ public function __construct($user = array()) * Initiate Log Class */ $this->log = new CentreonUserLog($this->user_id, $pearDB); - $this->userCrypted = md5($this->alias); /** * Init rest api auth From 598c0012e93203fe64ccf83a82fb8d83c5ae2435 Mon Sep 17 00:00:00 2001 From: Nouha-ElAbrouki <97687698+Noha-ElAbrouki@users.noreply.github.com> Date: Fri, 29 Jul 2022 10:17:32 +0200 Subject: [PATCH 03/18] enh(Header/userMenu):reduce spacing user menu (#11393) * update user menu --- www/front_src/src/Header/Clock/index.tsx | 6 +- .../Header/SwitchThemeMode/images/moon.svg | 3 - .../src/Header/SwitchThemeMode/images/sun.svg | 3 - .../src/Header/SwitchThemeMode/index.tsx | 135 ++++++++---------- .../SwitchThemeMode/useSwitchThemeMode.tsx | 30 ++++ www/front_src/src/Header/helpers/index.ts | 5 + www/front_src/src/Header/index.tsx | 17 ++- .../src/Header/userMenu/index.test.tsx | 6 +- www/front_src/src/Header/userMenu/index.tsx | 127 ++++++++++++---- 9 files changed, 210 insertions(+), 122 deletions(-) delete mode 100644 www/front_src/src/Header/SwitchThemeMode/images/moon.svg delete mode 100644 www/front_src/src/Header/SwitchThemeMode/images/sun.svg create mode 100644 www/front_src/src/Header/SwitchThemeMode/useSwitchThemeMode.tsx create mode 100644 www/front_src/src/Header/helpers/index.ts diff --git a/www/front_src/src/Header/Clock/index.tsx b/www/front_src/src/Header/Clock/index.tsx index 2a96282918e..2eaf875fa91 100755 --- a/www/front_src/src/Header/Clock/index.tsx +++ b/www/front_src/src/Header/Clock/index.tsx @@ -3,7 +3,7 @@ import { useRef, useState, useEffect } from 'react'; import { Typography } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; -import { useLocaleDateTimeFormat } from '@centreon/ui'; +import { centreonUi } from '../helpers/index'; const useStyles = makeStyles((theme) => ({ dateTime: { @@ -20,7 +20,7 @@ const Clock = (): JSX.Element => { time: '', }); - const { format, toTime } = useLocaleDateTimeFormat(); + const { format, toTime } = centreonUi.useLocaleDateTimeFormat(); const updateDateTime = (): void => { const now = new Date(); @@ -48,7 +48,7 @@ const Clock = (): JSX.Element => { const { date, time } = dateTime; return ( -
+
{date} {time}
diff --git a/www/front_src/src/Header/SwitchThemeMode/images/moon.svg b/www/front_src/src/Header/SwitchThemeMode/images/moon.svg deleted file mode 100644 index 4c16f826815..00000000000 --- a/www/front_src/src/Header/SwitchThemeMode/images/moon.svg +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/www/front_src/src/Header/SwitchThemeMode/images/sun.svg b/www/front_src/src/Header/SwitchThemeMode/images/sun.svg deleted file mode 100644 index c819f5b586d..00000000000 --- a/www/front_src/src/Header/SwitchThemeMode/images/sun.svg +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/www/front_src/src/Header/SwitchThemeMode/index.tsx b/www/front_src/src/Header/SwitchThemeMode/index.tsx index 8a3eca92bda..ef83ec81ebf 100644 --- a/www/front_src/src/Header/SwitchThemeMode/index.tsx +++ b/www/front_src/src/Header/SwitchThemeMode/index.tsx @@ -1,105 +1,70 @@ -import { equals } from 'ramda'; -import { useAtom } from 'jotai'; +import { useState } from 'react'; + +import clsx from 'clsx'; import { useLocation } from 'react-router-dom'; -import { styled } from '@mui/material/styles'; -import Switch from '@mui/material/Switch'; +import { ListItemText, Switch } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; -import { userAtom, ThemeMode } from '@centreon/ui-context'; import { patchData, useRequest } from '@centreon/ui'; -import svgSun from './images/sun.svg'; -import svgMoon from './images/moon.svg'; - -interface StyleProps { - darkModeSvg?: string; - lightModeSvg?: string; -} +import useSwitchThemeMode from './useSwitchThemeMode'; -const ThemeModeSwitch = styled(Switch, { - shouldForwardProp: (prop) => - !equals(prop, 'color') && - !equals(prop, 'lightModeSvg') && - !equals(prop, 'darkModeSvg'), -})(({ theme, darkModeSvg, lightModeSvg }) => ({ - '& .MuiSwitch-switchBase': { +const useStyles = makeStyles((theme) => ({ + container: { + '& .MuiSwitch-thumb': { + backgroundColor: 'white', + }, + '& .MuiSwitch-track': { + backgroundColor: '#aab4be', + opacity: 1, + }, + alignItems: 'center', + display: 'flex', + }, + containerMode: { + display: 'flex', + justifyContent: 'space-around', + }, + containerSwitch: { + '& .MuiSwitch-switchBase': { + padding: theme.spacing(0.5, 0.5, 0.5, 0.75), + }, '&.Mui-checked': { - '& + .MuiSwitch-track': { - backgroundColor: '#aab4be', - opacity: 1, - }, - '& .MuiSwitch-thumb:before': { - backgroundImage: `url(${darkModeSvg})`, + '&:hover': { + backgroundColor: 'unset', }, - color: 'transparent', - transform: 'translate(15px,-50%)', }, '&:hover': { - backgroundColor: 'transparent', + backgroundColor: 'unset', }, - color: 'black', - margin: 0, - position: 'absolute', - top: '50%', - transform: 'translate(-0.5px,-50%)', }, - '& .MuiSwitch-thumb': { - '&:before': { - backgroundImage: `url(${lightModeSvg})`, - backgroundPosition: 'center', - backgroundRepeat: 'no-repeat', - content: "''", - height: '100%', - left: theme.spacing(0), - position: 'absolute', - top: theme.spacing(0), - width: '100%', - }, - backgroundColor: 'white', - height: theme.spacing(3), - width: theme.spacing(3), + disabledMode: { + color: theme.palette.common.white, + opacity: 0.5, }, - '& .MuiSwitch-track': { - backgroundColor: '#aab4be', - borderRadius: theme.spacing(10 / 8), - opacity: 1, - }, - height: theme.spacing(32 / 8), - padding: theme.spacing(11 / 8, 4 / 8, 11 / 8, 9 / 8), - width: theme.spacing(50 / 8), -})); - -const useStyles = makeStyles(() => ({ - container: { - alignItems: 'center', - display: 'flex', + mode: { + paddingLeft: theme.spacing(1), }, })); const SwitchThemeMode = (): JSX.Element => { - const props = { - darkModeSvg: svgMoon, - lightModeSvg: svgSun, - }; const classes = useStyles(); const { pathname } = useLocation(); + const [isPending, isDarkMode, themeMode, updateUser] = useSwitchThemeMode(); + + const [isDark, setIsDark] = useState(isDarkMode); const { sendRequest } = useRequest({ request: patchData, }); - const [user, setUser] = useAtom(userAtom); - const isDarkMode = equals(user.themeMode, ThemeMode.dark); const switchEndPoint = './api/latest/configuration/users/current/parameters'; const switchThemeMode = (): void => { - const themeMode = isDarkMode ? ThemeMode.light : ThemeMode.dark; const isCurrentPageLegacy = pathname.includes('php'); - setUser({ - ...user, - themeMode, - }); + setIsDark(!isDark); + updateUser(); sendRequest({ data: { theme: themeMode }, endpoint: switchEndPoint, @@ -112,11 +77,29 @@ const SwitchThemeMode = (): JSX.Element => { return (
- +
+ + Light + + + + Dark + +
); }; diff --git a/www/front_src/src/Header/SwitchThemeMode/useSwitchThemeMode.tsx b/www/front_src/src/Header/SwitchThemeMode/useSwitchThemeMode.tsx new file mode 100644 index 00000000000..f25adff71f7 --- /dev/null +++ b/www/front_src/src/Header/SwitchThemeMode/useSwitchThemeMode.tsx @@ -0,0 +1,30 @@ +import { useTransition } from 'react'; + +import { useAtom } from 'jotai'; +import { equals } from 'ramda'; + +import { userAtom, ThemeMode } from '@centreon/ui-context'; + +const useSwitchThemeMode = (): [ + isDarkMode: boolean, + isPending: boolean, + themeMode: ThemeMode, + updateUser: () => void, +] => { + const [user, setUser] = useAtom(userAtom); + const isDarkMode = equals(user.themeMode, ThemeMode.dark); + const [isPending, startTransition] = useTransition(); + + const themeMode = isDarkMode ? ThemeMode.light : ThemeMode.dark; + const updateUser = (): void => + startTransition(() => { + setUser({ + ...user, + themeMode, + }); + }); + + return [isPending, isDarkMode, themeMode, updateUser]; +}; + +export default useSwitchThemeMode; diff --git a/www/front_src/src/Header/helpers/index.ts b/www/front_src/src/Header/helpers/index.ts new file mode 100644 index 00000000000..62d01d70451 --- /dev/null +++ b/www/front_src/src/Header/helpers/index.ts @@ -0,0 +1,5 @@ +import { useLocaleDateTimeFormat } from '@centreon/ui'; + +export const centreonUi = { + useLocaleDateTimeFormat, +}; diff --git a/www/front_src/src/Header/index.tsx b/www/front_src/src/Header/index.tsx index 3d1262da36d..dbf1e2c3cfd 100755 --- a/www/front_src/src/Header/index.tsx +++ b/www/front_src/src/Header/index.tsx @@ -1,3 +1,5 @@ +import { useRef } from 'react'; + import { makeStyles } from '@mui/styles'; import Hook from '../components/Hook'; @@ -6,7 +8,6 @@ import PollerMenu from './PollerMenu'; import HostStatusCounter from './RessourceStatusCounter/Host'; import ServiceStatusCounter from './RessourceStatusCounter/Service'; import UserMenu from './userMenu'; -import SwitchMode from './SwitchThemeMode'; const HookComponent = Hook as unknown as (props) => JSX.Element; @@ -30,12 +31,12 @@ const useStyles = makeStyles((theme) => ({ justifyContent: 'center', }, pollerContainer: { - flex: 0.5, + flex: 0.4, }, rightContainer: { alignItems: 'center', display: 'flex', - flex: 1.1, + flex: 0.9, }, serviceStatusContainer: { display: 'flex', @@ -49,16 +50,17 @@ const useStyles = makeStyles((theme) => ({ userMenuContainer: { alignItems: 'center', display: 'flex', - flex: 0.4, + flex: 0.3, justifyContent: 'flex-end', }, })); const Header = (): JSX.Element => { const classes = useStyles(); + const headerRef = useRef(null); return ( -
+
@@ -74,10 +76,7 @@ const Header = (): JSX.Element => {
- -
- -
+
diff --git a/www/front_src/src/Header/userMenu/index.test.tsx b/www/front_src/src/Header/userMenu/index.test.tsx index 471ef21a908..2878468f19a 100644 --- a/www/front_src/src/Header/userMenu/index.test.tsx +++ b/www/front_src/src/Header/userMenu/index.test.tsx @@ -109,11 +109,9 @@ describe('User Menu', () => { userEvent.click(screen.getByLabelText(labelProfile)); await waitFor(() => { - expect(screen.getByText('Admin admin')).toBeInTheDocument(); + expect(screen.getByText('admin')).toBeInTheDocument(); }); - expect(screen.getByText('as admin')).toBeInTheDocument(); - await waitFor(() => { expect(screen.getByText('1:20 PM')).toBeInTheDocument(); }); @@ -139,7 +137,7 @@ describe('User Menu', () => { }); await waitFor(() => { - expect(screen.getByText('Admin admin')).toBeInTheDocument(); + expect(screen.getByText('admin')).toBeInTheDocument(); }); userEvent.click(screen.getByText(labelCopyAutologinLink)); diff --git a/www/front_src/src/Header/userMenu/index.tsx b/www/front_src/src/Header/userMenu/index.tsx index 45adb4fc4e7..9b66220f892 100755 --- a/www/front_src/src/Header/userMenu/index.tsx +++ b/www/front_src/src/Header/userMenu/index.tsx @@ -6,6 +6,8 @@ import { useNavigate } from 'react-router-dom'; import { useUpdateAtom } from 'jotai/utils'; import { gt, isNil, not, __ } from 'ramda'; +import { grey } from '@mui/material/colors'; +import Divider from '@mui/material/Divider'; import { Typography, Paper, @@ -35,6 +37,7 @@ import { useLocaleDateTimeFormat, } from '@centreon/ui'; +import SwitchMode from '../SwitchThemeMode/index'; import Clock from '../Clock'; import useNavigation from '../../Navigation/useNavigation'; import { areUserParametersLoadedAtom } from '../../Main/useUser'; @@ -79,6 +82,27 @@ const ListItemIcon = styled(MUIListItemIcon)(({ theme }) => ({ })); const useStyles = makeStyles((theme) => ({ + button: { + '&:hover': { + '&:after': { + backgroundColor: theme.palette.common.white, + content: '""', + height: '100%', + left: 0, + opacity: 0.08, + position: 'absolute', + right: 0, + top: 0, + }, + }, + }, + containerList: { + padding: theme.spacing(0.5, 0, 0.5, 0), + }, + divider: { + borderColor: grey[600], + margin: theme.spacing(0, 1.25, 0, 1.25), + }, fullname: { overflow: 'hidden', textOverflow: 'ellipsis', @@ -91,14 +115,23 @@ const useStyles = makeStyles((theme) => ({ top: theme.spacing(-13), width: theme.spacing(0), }, + icon: { + minWidth: theme.spacing(3.75), + }, loaderUserMenu: { - marginRight: 22, + marginRight: theme.spacing(22 / 8), }, menu: { backgroundColor: theme.palette.common.black, + borderRadius: 0, color: theme.palette.common.white, - maxWidth: 230, - width: '100%', + minWidth: 190, + }, + menuItem: { + padding: theme.spacing(0, 2, 0.25, 2), + }, + nameContainer: { + padding: theme.spacing(0, 2, 0.25, 2.25), }, passwordExpiration: { color: theme.palette.warning.main, @@ -107,6 +140,9 @@ const useStyles = makeStyles((theme) => ({ overflow: 'hidden', zIndex: theme.zIndex.tooltip, }, + switchItem: { + padding: theme.spacing(0, 2, 0.25, 11 / 8), + }, text: { overflow: 'hidden', textOverflow: 'ellipsis', @@ -119,6 +155,7 @@ const useStyles = makeStyles((theme) => ({ }, wrapRightUser: { alignItems: 'center', + background: theme.palette.common.black, display: 'flex', flexWrap: 'wrap', marginLeft: theme.spacing(0.5), @@ -131,8 +168,11 @@ const useStyles = makeStyles((theme) => ({ justifyContent: 'flex-end', }, })); +interface Props { + headerRef?: RefObject; +} -const UserMenu = (): JSX.Element => { +const UserMenu = ({ headerRef }: Props): JSX.Element => { const classes = useStyles(); const { t } = useTranslation(); const { allowedPages } = useNavigation(); @@ -140,10 +180,12 @@ const UserMenu = (): JSX.Element => { const [copied, setCopied] = useState(false); const [data, setData] = useState(null); const [anchorEl, setAnchorEl] = useState(null); + const [anchorHeight, setAnchorHeight] = useState(12); const profile = useRef(); const userMenu = useRef(); const autologinNode = useRef(); const refreshTimeout = useRef(); + const userIconRef = useRef(null); const { sendRequest: logoutRequest } = useRequest({ request: postData, }); @@ -198,6 +240,21 @@ const UserMenu = (): JSX.Element => { }, 60000); }; + const getPositionOfPopper = (): void => { + if (isNil(headerRef?.current) || isNil(userIconRef?.current)) { + return; + } + const headerHeight = headerRef?.current?.getBoundingClientRect()?.height; + + const userMenuBottom = + userIconRef?.current?.getBoundingClientRect()?.bottom; + + if (isNil(headerHeight)) { + return; + } + setAnchorHeight(headerHeight - userMenuBottom); + }; + const toggle = (event: MouseEvent): void => { if (anchorEl) { setAnchorEl(null); @@ -205,6 +262,7 @@ const UserMenu = (): JSX.Element => { return; } setAnchorEl(event.currentTarget); + getPositionOfPopper(); }; const closeUserMenu = (): void => { @@ -246,10 +304,14 @@ const UserMenu = (): JSX.Element => { useEffect(() => { window.addEventListener('mousedown', handleClick, false); + window.addEventListener('resize', getPositionOfPopper); + loadUserData(); return (): void => { window.removeEventListener('mousedown', handleClick, false); + window.removeEventListener('resize', getPositionOfPopper); + if (refreshTimeout.current) { clearTimeout(refreshTimeout.current); } @@ -279,9 +341,9 @@ const UserMenu = (): JSX.Element => { }; return ( -
+
} > @@ -302,8 +364,10 @@ const UserMenu = (): JSX.Element => { > @@ -312,8 +376,16 @@ const UserMenu = (): JSX.Element => { transition anchorEl={anchorEl} className={classes.popper} + data-cy="popper" + modifiers={[ + { + name: 'offset', + options: { + offset: [22, anchorHeight], + }, + }, + ]} open={not(isNil(anchorEl))} - placement="bottom-end" > {({ TransitionProps }): JSX.Element => ( @@ -324,21 +396,18 @@ const UserMenu = (): JSX.Element => { display: isNil(anchorEl) ? 'none' : 'block', }} > - - + + - {data.fullname} + {data.username} - - {`${t('as')} ${data.username}`} - + + {not(passwordIsNotYetAboutToExpire) && ( - +
{t(labelPasswordWillExpireIn)}: @@ -350,11 +419,12 @@ const UserMenu = (): JSX.Element => { )} {allowEditProfile && ( - + - + {t(labelEditProfile)} @@ -362,9 +432,9 @@ const UserMenu = (): JSX.Element => { )} {data.autologinkey && ( - + - + {copied ? ( ) : ( @@ -384,9 +454,18 @@ const UserMenu = (): JSX.Element => { /> )} - - - +
+ +
+ + + + + + {t(labelLogout)} From 7737c376d240bc33e21300b8b1a8d4d55666f3e8 Mon Sep 17 00:00:00 2001 From: jeremyjaouen <61694165+jeremyjaouen@users.noreply.github.com> Date: Fri, 29 Jul 2022 10:24:52 +0200 Subject: [PATCH 04/18] fix(hostgroup): fix display of hostgroups in select2 (#11431) (#11443) --- www/class/centreonHostgroups.class.php | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/www/class/centreonHostgroups.class.php b/www/class/centreonHostgroups.class.php index c8c5b1225a2..34dd68d6947 100644 --- a/www/class/centreonHostgroups.class.php +++ b/www/class/centreonHostgroups.class.php @@ -334,6 +334,12 @@ public function getObjectForSelect2($values = array(), $options = array()) return $items; } + $hostgroups = []; + // $values structure: ['1,2,3,4'], keeping the foreach in case it could have more than one index + foreach ($values as $value) { + $hostgroups = array_merge($hostgroups, explode(',', $value)); + } + // get list of authorized hostgroups if (!$centreon->user->access->admin) { $hgAcl = $centreon->user->access->getHostGroupAclConf( @@ -347,7 +353,7 @@ public function getObjectForSelect2($values = array(), $options = array()) 'conditions' => array( 'hostgroup.hg_id' => array( 'IN', - $values + $hostgroups ) ) ), @@ -359,15 +365,13 @@ public function getObjectForSelect2($values = array(), $options = array()) $listValues = ''; $queryValues = array(); - foreach ($values as $k => $v) { - //As it happens that $v could be like "X,Y" when two hostgroups are selected, we added a second foreach - $multiValues = explode(',', $v); - foreach ($multiValues as $item) { - $ids = explode('-', $item); - $listValues .= ':hgId_' . $ids[0] . ', '; - $queryValues['hgId_' . $ids[0]] = (int)$ids[0]; - } + foreach ($hostgroups as $item) { + // the below explode may not be useful + $ids = explode('-', $item); + $listValues .= ':hgId_' . $ids[0] . ', '; + $queryValues['hgId_' . $ids[0]] = (int)$ids[0]; } + $listValues = rtrim($listValues, ', '); $query = 'SELECT hg_id, hg_name FROM hostgroup WHERE hg_id IN (' . $listValues . ') ORDER BY hg_name '; $stmt = $this->DB->prepare($query); From 038b6fca98f74fc7502615df8a2636b756ee41f7 Mon Sep 17 00:00:00 2001 From: Kevin Duret Date: Fri, 29 Jul 2022 11:03:34 +0200 Subject: [PATCH 05/18] fix(ci): fix debian packaging with freshly instanciated jenkins slave (#11398) (#11399) Refs: MON-14377 --- ci/scripts/centreon-deb-package.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ci/scripts/centreon-deb-package.sh b/ci/scripts/centreon-deb-package.sh index d7a1cdcca36..01d7bafa875 100755 --- a/ci/scripts/centreon-deb-package.sh +++ b/ci/scripts/centreon-deb-package.sh @@ -38,10 +38,9 @@ for i in lang/* ; do done rm -rf lang -# Generate API documentation. -apt install -y npm && sleep 30 -npm install -g redoc-cli -/usr/local/bin/redoc-cli bundle --options.hideDownloadButton=true doc/API/centreon-api-v${MAJOR_VERSION}.yaml -o ../centreon-api-v${MAJOR_VERSION}.html +# Install npm dependency +apt-get update +apt install -y npm # Make tar with original content cd .. From 35967db531c1ab9421aed1da17c9d2041127e40e Mon Sep 17 00:00:00 2001 From: Elmahdi ABBASSI <108519266+emabassi-ext@users.noreply.github.com> Date: Fri, 29 Jul 2022 10:29:10 +0100 Subject: [PATCH 06/18] Sanitized and bound queries (#11413) (#11445) lines : 130 -142 --- .../hostgroup_dependency/DB-Func.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/www/include/configuration/configObject/hostgroup_dependency/DB-Func.php b/www/include/configuration/configObject/hostgroup_dependency/DB-Func.php index ded4d3c58ff..1ddeac48d46 100644 --- a/www/include/configuration/configObject/hostgroup_dependency/DB-Func.php +++ b/www/include/configuration/configObject/hostgroup_dependency/DB-Func.php @@ -124,10 +124,12 @@ function multipleHostGroupDependencyInDB($dependencies = array(), $nbrDup = arra "WHERE dependency_dep_id = '" . $key . "'"; $dbResult = $pearDB->query($query); $fields["dep_hgParents"] = ""; + $query = "INSERT INTO dependency_hostgroupParent_relation VALUES (:max_id, :hg_id)"; + $statement = $pearDB->prepare($query); while ($hg = $dbResult->fetch()) { - $query = "INSERT INTO dependency_hostgroupParent_relation VALUES ('" . - $maxId["MAX(dep_id)"] . "', '" . $hg["hostgroup_hg_id"] . "')"; - $pearDB->query($query); + $statement->bindValue(':max_id', (int) $maxId["MAX(dep_id)"], \PDO::PARAM_INT); + $statement->bindValue(':hg_id', (int) $hg["hostgroup_hg_id"], \PDO::PARAM_INT); + $statement->execute(); $fields["dep_hgParents"] .= $hg["hostgroup_hg_id"] . ","; } $fields["dep_hgParents"] = trim($fields["dep_hgParents"], ","); @@ -136,10 +138,12 @@ function multipleHostGroupDependencyInDB($dependencies = array(), $nbrDup = arra "WHERE dependency_dep_id = '" . $key . "'"; $dbResult = $pearDB->query($query); $fields["dep_hgChilds"] = ""; + $query = "INSERT INTO dependency_hostgroupChild_relation VALUES (:max_id, :hg_id)"; + $statement = $pearDB->prepare($query); while ($hg = $dbResult->fetch()) { - $query = "INSERT INTO dependency_hostgroupChild_relation VALUES ('" . - $maxId["MAX(dep_id)"] . "', '" . $hg["hostgroup_hg_id"] . "')"; - $pearDB->query($query); + $statement->bindValue(':max_id', (int) $maxId["MAX(dep_id)"], \PDO::PARAM_INT); + $statement->bindValue(':hg_id', (int) $hg["hostgroup_hg_id"], \PDO::PARAM_INT); + $statement->execute(); $fields["dep_hgChilds"] .= $hg["hostgroup_hg_id"] . ","; } $fields["dep_hgChilds"] = trim($fields["dep_hgChilds"], ","); From ddd8d277bc257ef4bc8b53bc523f4a3fa1c1f358 Mon Sep 17 00:00:00 2001 From: hyahiaoui-ext <97593234+hyahiaoui-ext@users.noreply.github.com> Date: Fri, 29 Jul 2022 11:07:42 +0100 Subject: [PATCH 07/18] Snyk: Sanitize and bind media sync queries 22.04.x (#11418) * sanitizing and binding sync dir file queries * Applying some fixes --- www/include/options/media/images/syncDir.php | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/www/include/options/media/images/syncDir.php b/www/include/options/media/images/syncDir.php index b9eee03bb0c..f6e2075a36a 100644 --- a/www/include/options/media/images/syncDir.php +++ b/www/include/options/media/images/syncDir.php @@ -173,12 +173,17 @@ function checkPicture($picture, $dirpath, $dir_id, $pearDB) $gdCounter++; } - $DBRESULT = $pearDB->query("SELECT img_id " . + $statement = $pearDB->prepare( + "SELECT img_id " . "FROM view_img, view_img_dir_relation vidh " . - "WHERE img_path = '" . $picture . "' " . - " AND vidh.dir_dir_parent_id = '" . $dir_id . "'" . - " AND vidh.img_img_id = img_id"); - if (!$DBRESULT->rowCount()) { + "WHERE img_path = :img_path " . + "AND vidh.dir_dir_parent_id = :dir_dir_parent_id " . + "AND vidh.img_img_id = img_id" + ); + $statement->bindValue(':img_path', $picture, \PDO::PARAM_STR); + $statement->bindValue(':dir_dir_parent_id', (int) $dir_id, \PDO::PARAM_INT); + $statement->execute(); + if (!$statement->rowCount()) { $DBRESULT = $pearDB->query( "INSERT INTO view_img (`img_name`, `img_path`) VALUES ('" . $img_info["filename"] . "', '" . $picture . "')" @@ -189,13 +194,16 @@ function checkPicture($picture, $dirpath, $dir_id, $pearDB) ); $data = $DBRESULT->fetchRow(); $regCounter++; - $DBRESULT = $pearDB->query( - "INSERT INTO view_img_dir_relation (`dir_dir_parent_id`, `img_img_id`) VALUES ('" - . $dir_id . "', '" . $data['img_id'] . "')" + $statement = $pearDB->prepare( + "INSERT INTO view_img_dir_relation (`dir_dir_parent_id`, `img_img_id`) + VALUES (:dir_dir_parent_id, :img_img_id)" ); + $statement->bindValue(':dir_dir_parent_id', (int) $dir_id, \PDO::PARAM_INT); + $statement->bindValue(':img_img_id', (int) $data['img_id'], \PDO::PARAM_INT); + $statement->execute(); return $data['img_id']; } else { - $data = $DBRESULT->fetchRow(); + $data = $statement->fetchRow(\PDO::FETCH_ASSOC); return 0; } } @@ -211,9 +219,11 @@ function DeleteOldPictures($pearDB) . "view_img_dir vid, view_img_dir_relation vidr " . "WHERE vidr.img_img_id = vi.img_id AND vid.dir_id = vidr.dir_dir_parent_id" ); + $statement = $pearDB->prepare("DELETE FROM view_img WHERE img_id = :img_id"); while ($row2 = $DBRESULT->fetchRow()) { if (!file_exists("./img/media/" . $row2["dir_alias"] . "/" . $row2["img_path"])) { - $pearDB->query("DELETE FROM view_img WHERE img_id = '" . $row2["img_id"] . "'"); + $statement->bindValue(':img_id', (int) $row2["img_id"], \PDO::PARAM_INT); + $statement->execute(); $fileRemoved++; } } From 9db3d7ed529e5fb995fe073c075df82d292e157b Mon Sep 17 00:00:00 2001 From: hyahiaoui-ext <97593234+hyahiaoui-ext@users.noreply.github.com> Date: Fri, 29 Jul 2022 11:08:45 +0100 Subject: [PATCH 08/18] Snyk: Sanitize and bind ACL service dependency queries dev-22.04.x (#11395) --- .../service_dependency/DB-Func.php | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/www/include/configuration/configObject/service_dependency/DB-Func.php b/www/include/configuration/configObject/service_dependency/DB-Func.php index 501d43c5f2a..573e2b5e62b 100644 --- a/www/include/configuration/configObject/service_dependency/DB-Func.php +++ b/www/include/configuration/configObject/service_dependency/DB-Func.php @@ -127,10 +127,12 @@ function multipleServiceDependencyInDB($dependencies = array(), $nbrDup = array( $query = "SELECT * FROM dependency_hostChild_relation WHERE dependency_dep_id = '" . $key . "'"; $dbResult = $pearDB->query($query); $fields["dep_hostPar"] = ""; + $query = "INSERT INTO dependency_hostChild_relation VALUES (:dep_id, :host_host_id)"; + $statement = $pearDB->prepare($query); while ($host = $dbResult->fetch()) { - $query = "INSERT INTO dependency_hostChild_relation VALUES ('" . $maxId["MAX(dep_id)"] . - "', '" . $host["host_host_id"] . "')"; - $pearDB->query($query); + $statement->bindValue(':dep_id', (int) $maxId["MAX(dep_id)"], \PDO::PARAM_INT); + $statement->bindValue(':host_host_id', (int) $host["host_host_id"], \PDO::PARAM_INT); + $statement->execute(); $fields["dep_hostPar"] .= $host["host_host_id"] . ","; } $fields["dep_hostPar"] = trim($fields["dep_hostPar"], ","); @@ -138,21 +140,36 @@ function multipleServiceDependencyInDB($dependencies = array(), $nbrDup = array( $query = "SELECT * FROM dependency_serviceParent_relation WHERE dependency_dep_id = '" . $key . "'"; $dbResult = $pearDB->query($query); $fields["dep_hSvPar"] = ""; + $query = "INSERT INTO dependency_serviceParent_relation + VALUES (:dep_id, :service_service_id, :host_host_id)"; + $statement = $pearDB->prepare($query); while ($service = $dbResult->fetch()) { - $query = "INSERT INTO dependency_serviceParent_relation VALUES ('" . - $maxId["MAX(dep_id)"] . "', '" . $service["service_service_id"] . "', '" . - $service["host_host_id"] . "')"; - $pearDB->query($query); + $statement->bindValue(':dep_id', (int) $maxId["MAX(dep_id)"], \PDO::PARAM_INT); + $statement->bindValue( + ':service_service_id', + (int) $service["service_service_id"], + \PDO::PARAM_INT + ); + $statement->bindValue(':host_host_id', (int) $service["host_host_id"], \PDO::PARAM_INT); + $statement->execute(); $fields["dep_hSvPar"] .= $service["service_service_id"] . ","; } $fields["dep_hSvPar"] = trim($fields["dep_hSvPar"], ","); $query = "SELECT * FROM dependency_serviceChild_relation WHERE dependency_dep_id = '" . $key . "'"; $dbResult = $pearDB->query($query); $fields["dep_hSvChi"] = ""; + $query = "INSERT INTO dependency_serviceChild_relation + VALUES (:dep_id, :service_service_id, :host_host_id)"; + $statement = $pearDB->prepare($query); while ($service = $dbResult->fetch()) { - $query = "INSERT INTO dependency_serviceChild_relation VALUES ('" . $maxId["MAX(dep_id)"] . - "', '" . $service["service_service_id"] . "', '" . $service["host_host_id"] . "')"; - $pearDB->query($query); + $statement->bindValue(':dep_id', (int) $maxId["MAX(dep_id)"], \PDO::PARAM_INT); + $statement->bindValue( + ':service_service_id', + (int) $service["service_service_id"], + \PDO::PARAM_INT + ); + $statement->bindValue(':host_host_id', (int) $service["host_host_id"], \PDO::PARAM_INT); + $statement->execute(); $fields["dep_hSvChi"] .= $service["service_service_id"] . ","; } $fields["dep_hSvChi"] = trim($fields["dep_hSvChi"], ","); From 9b61eb73775ed2739139d891040b0a4f833effb7 Mon Sep 17 00:00:00 2001 From: hyahiaoui-ext <97593234+hyahiaoui-ext@users.noreply.github.com> Date: Fri, 29 Jul 2022 11:09:13 +0100 Subject: [PATCH 09/18] Snyk: Sanitize and bind Auth class queries 22.04.x (#11448) --- www/class/centreonAuth.class.php | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/www/class/centreonAuth.class.php b/www/class/centreonAuth.class.php index a2ea42427bb..e05ca3ce06f 100644 --- a/www/class/centreonAuth.class.php +++ b/www/class/centreonAuth.class.php @@ -339,12 +339,13 @@ protected function checkUser($username, $password, $token) if ($dbResult->rowCount()) { $this->userInfos = $dbResult->fetch(); if ($this->userInfos["default_page"]) { - $dbResult2 = $this->pearDB->query( - "SELECT topology_url_opt FROM topology WHERE topology_page = " - . $this->userInfos["default_page"] + $statement = $this->pearDB->prepare( + "SELECT topology_url_opt FROM topology WHERE topology_page = :topology_page" ); - if ($dbResult2->numRows()) { - $data = $dbResult2->fetch(); + $statement->bindValue(':topology_page', (int) $this->userInfos["default_page"], \PDO::PARAM_INT); + $statement->execute(); + if ($statement->rowCount()) { + $data = $statement->fetch(\PDO::FETCH_ASSOC); $this->userInfos["default_page"] .= $data["topology_url_opt"]; } } @@ -382,20 +383,23 @@ protected function checkUser($username, $password, $token) /* * Reset userInfos with imported information */ - $dbResult = $this->pearDB->query( + $statement = $this->pearDB->prepare( "SELECT * FROM `contact` " . - "WHERE `contact_alias` = '" . $this->pearDB->escape($username, true) . "'" . + "WHERE `contact_alias` = :contact_alias" . "AND `contact_activate` = '1' AND `contact_register` = '1' LIMIT 1" ); - if ($dbResult->rowCount()) { - $this->userInfos = $dbResult->fetch(); + $statement->bindValue(':contact_alias', $this->pearDB->escape($username, true), \PDO::PARAM_STR); + $statement->execute(); + if ($statement->rowCount()) { + $this->userInfos = $statement->fetch(\PDO::FETCH_ASSOC); if ($this->userInfos["default_page"]) { - $dbResult2 = $this->pearDB->query( - "SELECT topology_url_opt FROM topology WHERE topology_page = " - . $this->userInfos["default_page"] + $statement = $this->pearDB->prepare( + "SELECT topology_url_opt FROM topology WHERE topology_page = :topology_page" ); - if ($dbResult2->numRows()) { - $data = $dbResult2->fetch(); + $statement->bindValue(':topology_page', (int) $this->userInfos["default_page"], \PDO::PARAM_INT); + $statement->execute(); + if ($statement->rowCount()) { + $data = $statement->fetch(\PDO::FETCH_ASSOC); $this->userInfos["default_page"] .= $data["topology_url_opt"]; } } From 8d64b02775048277fe469e79c6c8d639df6c7c59 Mon Sep 17 00:00:00 2001 From: Tom Darneix Date: Fri, 29 Jul 2022 12:20:13 +0200 Subject: [PATCH 10/18] [Backport/need review] fix(UI): Fix layout for Safari and form validation (#11440) * fix(UI): Fix layout for Safari and form validation (#11373) * Fix form validation * Fix padlock layout for safari * Update centreon-frontend * Remove debug variable * Fix test * Fix page respsoniveness * Rename variable * update deps * Fix package-lock * Fix package-lock * Add debug statement for debian * Install nodejs rather npm * Attempt fix * Attempt to fix nodejs installation * add sudo * Fix redoc-cli usage * Try to fix permission on npm * Fix * Fix permission * Fix permission (please work) * Fix source * Stop using npx because..... * Allow legacy-peer-deps * Remove nodejs installation * Fix image to pull for debian 11 --- Jenkinsfile | 2 +- ci/debian/rules | 2 +- ci/scripts/centreon-deb-package.sh | 6 +- .../src/Authentication/Openid/Form/inputs.ts | 18 +---- .../src/Authentication/Openid/index.test.tsx | 41 +--------- .../Openid/useValidationSchema.ts | 11 +-- www/front_src/src/Authentication/index.tsx | 74 ++++++++++++------- 7 files changed, 56 insertions(+), 98 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 0703e55f23c..e5895522af5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -308,7 +308,7 @@ try { checkout scm } sh 'rm -rf *.deb' - sh 'docker run -i --entrypoint /src/centreon/ci/scripts/centreon-deb-package.sh -w "/src" -v "$PWD:/src" -e DISTRIB="bullseye" -e VERSION=$VERSION -e RELEASE=$RELEASE registry.centreon.com/centreon-debian11-dependencies:22.04' + sh 'docker run -i --entrypoint /src/centreon/ci/scripts/centreon-deb-package.sh -w "/src" -v "$PWD:/src" -e DISTRIB="bullseye" -e VERSION=$VERSION -e RELEASE=$RELEASE registry.centreon.com/mon-build-dependencies-22.04:debian11' stash name: 'Debian11', includes: '*.deb' archiveArtifacts artifacts: "*" sh 'rm -rf *.deb' diff --git a/ci/debian/rules b/ci/debian/rules index 0e8ee8a1a3c..287f52a3658 100644 --- a/ci/debian/rules +++ b/ci/debian/rules @@ -13,7 +13,7 @@ override_dh_clean: override_dh_auto_build: composer install --no-dev --optimize-autoloader -n - npm ci + npm ci --legacy-peer-deps npm run build find . -type f | \ grep -v debian/extra/centreon-web/centreon-macroreplacement.txt | \ diff --git a/ci/scripts/centreon-deb-package.sh b/ci/scripts/centreon-deb-package.sh index 01d7bafa875..be700cfccb0 100755 --- a/ci/scripts/centreon-deb-package.sh +++ b/ci/scripts/centreon-deb-package.sh @@ -38,9 +38,9 @@ for i in lang/* ; do done rm -rf lang -# Install npm dependency -apt-get update -apt install -y npm +# Generate API documentation. +npm i -g redoc-cli +redoc-cli build --options.hideDownloadButton=true doc/API/centreon-api-v${MAJOR_VERSION}.yaml -o ../centreon-api-v${MAJOR_VERSION}.html # Make tar with original content cd .. diff --git a/www/front_src/src/Authentication/Openid/Form/inputs.ts b/www/front_src/src/Authentication/Openid/Form/inputs.ts index 01c07962a97..55b3b1fa125 100644 --- a/www/front_src/src/Authentication/Openid/Form/inputs.ts +++ b/www/front_src/src/Authentication/Openid/Form/inputs.ts @@ -1,4 +1,4 @@ -import { equals, isEmpty, isNil, not, path, prop } from 'ramda'; +import { equals, isEmpty, not, prop } from 'ramda'; import { FormikValues } from 'formik'; import { @@ -32,7 +32,7 @@ import { labelDeleteRelation, labelAuthorizationKey, } from '../translatedLabels'; -import { AuthenticationType, AuthorizationRule } from '../models'; +import { AuthenticationType } from '../models'; import { InputProps, InputType } from '../../FormInputs/models'; import { labelActivation, @@ -249,20 +249,6 @@ export const inputs: Array = [ claimValue: '', }, deleteLabel: labelDeleteRelation, - getRequired: ({ values, index }): boolean => { - const tableValues = prop('authorizationRules', values); - - const rowValues = path( - ['authorizationRules', index], - values, - ); - - return isNil(prop('contactGroup', values)) - ? not(isNil(rowValues)) - : isNil(tableValues) || - isEmpty(rowValues?.claimValue) || - isNil(rowValues?.accessGroup); - }, }, label: labelDefineRelationAuthorizationValueAndAccessGroup, type: InputType.FieldsTable, diff --git a/www/front_src/src/Authentication/Openid/index.test.tsx b/www/front_src/src/Authentication/Openid/index.test.tsx index d7fffde1689..ab9b4efb751 100644 --- a/www/front_src/src/Authentication/Openid/index.test.tsx +++ b/www/front_src/src/Authentication/Openid/index.test.tsx @@ -445,11 +445,10 @@ describe('Openid configuration form', () => { accessGroupsEndpoint, labelAccessGroup, 'Access Group 2', - 1, ], ])( 'updates the %p field when an option is selected from the retrieved options', - async (_, retrievedOptions, endpoint, label, value, index = 0) => { + async (_, retrievedOptions, endpoint, label, value) => { mockGetRequestsWithNoAuthorizationConfiguration(); renderOpenidConfigurationForm(); @@ -484,7 +483,7 @@ describe('Openid configuration form', () => { userEvent.click(screen.getByText(value)); await waitFor(() => { - expect(screen.getAllByLabelText(label)[index]).toHaveValue(value); + expect(screen.getAllByLabelText(label)[0]).toHaveValue(value); }); }, ); @@ -508,40 +507,4 @@ describe('Openid configuration form', () => { ); }); }); - - it('displays the "Authorization value" and "Access group" fields as required when the "Contact group" field is filled', async () => { - mockGetRequestsWithNoAuthorizationConfiguration(); - mockedAxios.get.mockResolvedValueOnce({ - data: retrievedContactGroups, - }); - - renderOpenidConfigurationForm(); - - await waitFor(() => { - expect(screen.getByLabelText(labelContactGroup)).toBeInTheDocument(); - }); - - userEvent.click(screen.getByLabelText(labelContactGroup)); - - await waitFor(() => { - expect(mockedAxios.get).toHaveBeenCalledWith( - `${contactGroupsEndpoint}?page=1&sort_by=${encodeURIComponent( - '{"name":"ASC"}', - )}`, - cancelTokenRequestParam, - ); - }); - - await waitFor(() => { - expect(screen.getByText('Contact Group 1')).toBeInTheDocument(); - }); - - userEvent.click(screen.getByText('Contact Group 1')); - - await waitFor(() => { - expect(screen.getByLabelText(labelAuthorizationValue)).toHaveAttribute( - 'required', - ); - }); - }); }); diff --git a/www/front_src/src/Authentication/Openid/useValidationSchema.ts b/www/front_src/src/Authentication/Openid/useValidationSchema.ts index 4f703165f68..038812dadee 100644 --- a/www/front_src/src/Authentication/Openid/useValidationSchema.ts +++ b/www/front_src/src/Authentication/Openid/useValidationSchema.ts @@ -6,7 +6,6 @@ import { labelRequired, labelInvalidURL, labelInvalidIPAddress, - labelAtLeastOneAuthorizationIsRequired, } from './translatedLabels'; const IPAddressRegexp = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,3})?$/; @@ -29,15 +28,7 @@ const useValidationSchema = (): Yup.SchemaOf => { return Yup.object({ authenticationType: Yup.string().required(t(labelRequired)), authorizationEndpoint: Yup.string().nullable().required(t(labelRequired)), - authorizationRules: Yup.array() - .of(authorizationSchema) - .when('contactGroup', (contactGroup, schema) => { - return contactGroup - ? schema - .min(1, t(labelAtLeastOneAuthorizationIsRequired)) - .required(t(labelRequired)) - : schema.nullable(); - }), + authorizationRules: Yup.array().of(authorizationSchema), autoImport: Yup.boolean().required(t(labelRequired)), baseUrl: Yup.string() .matches(urlRegexp, t(labelInvalidURL)) diff --git a/www/front_src/src/Authentication/index.tsx b/www/front_src/src/Authentication/index.tsx index bf12422f160..71668d687f8 100644 --- a/www/front_src/src/Authentication/index.tsx +++ b/www/front_src/src/Authentication/index.tsx @@ -1,9 +1,8 @@ -import { useMemo } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useAtomValue } from 'jotai'; import { useUpdateAtom } from 'jotai/utils'; -import { Responsive } from '@visx/visx'; import { Box, Container, Paper, Tab } from '@mui/material'; import { TabContext, TabList, TabPanel } from '@mui/lab'; @@ -90,35 +89,39 @@ const useStyles = makeStyles((theme) => ({ formContainer: { display: 'grid', gridTemplateColumns: '1.2fr 0.6fr', - height: '100%', - overflowY: 'auto', + justifyItems: 'center', padding: theme.spacing(3), }, image: { + height: '200px', opacity: 0.5, padding: theme.spacing(0, 5), position: 'sticky', top: 0, + width: '200px', }, panel: { - height: '80%', padding: 0, }, paper: { boxShadow: theme.shadows[3], - height: '100%', }, tabList: { boxShadow: theme.shadows[2], }, })); -const marginBottomHeight = 88; +const scrollMargin = 8; const Authentication = (): JSX.Element => { const classes = useStyles(); const { t } = useTranslation(); + const formContainerRef = useRef(null); + + const [windowHeight, setWindowHeight] = useState(window.innerHeight); + const [clientRect, setClientRect] = useState(null); + const appliedTab = useAtomValue(appliedTabAtom); const { themeMode } = useAtomValue(userAtom); const setTab = useUpdateAtom(tabAtom); @@ -127,6 +130,23 @@ const Authentication = (): JSX.Element => { setTab(newTab); }; + const resize = (): void => { + setWindowHeight(window.innerHeight); + }; + + useEffect(() => { + window.addEventListener('resize', resize); + + setClientRect(formContainerRef.current?.getBoundingClientRect() ?? null); + + return () => { + window.removeEventListener('resize', resize); + }; + }, []); + + const formContainerHeight = + windowHeight - (clientRect?.top || 0) - scrollMargin; + const tabs = useMemo( () => panels.map(({ title, value }) => ( @@ -136,33 +156,31 @@ const Authentication = (): JSX.Element => { ); const tabPanels = useMemo( - () => ( - - {({ height }): Array => - panels.map(({ Component, value, image }) => ( - -
- - padlock -
-
- )) - } -
- ), - [themeMode], + () => + panels.map(({ Component, value, image }) => ( + + +
+ + padlock +
+
+
+ )), + [themeMode, formContainerHeight], ); return ( - + Date: Fri, 29 Jul 2022 11:52:57 +0100 Subject: [PATCH 11/18] [SNYK] Sanitize and bind centreonGraph class queries (#11409) (#11421) 1122 1153 1134 --- www/class/centreonGraph.class.php | 32 ++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/www/class/centreonGraph.class.php b/www/class/centreonGraph.class.php index fd55822883a..4ae3ef297d8 100644 --- a/www/class/centreonGraph.class.php +++ b/www/class/centreonGraph.class.php @@ -1076,16 +1076,18 @@ private function getDefaultGraphTemplate() return; } else { $command_id = getMyServiceField($this->indexData["service_id"], "command_command_id"); - $DBRESULT = $this->DB->query("SELECT graph_id FROM command WHERE `command_id` = '" . $command_id . "'"); - if ($DBRESULT->rowCount()) { - $data = $DBRESULT->fetch(); + $statement = $this->DB->prepare("SELECT graph_id FROM command WHERE `command_id` = :command_id"); + $statement->bindValue(':command_id', (int) $command_id, \PDO::PARAM_INT); + $statement->execute(); + if ($statement->rowCount()) { + $data = $statement->fetch(); if ($data["graph_id"] != 0) { $this->templateId = $data["graph_id"]; unset($data); return; } } - $DBRESULT->closeCursor(); + $statement->closeCursor(); unset($command_id); } $DBRESULT = $this->DB->query("SELECT graph_id FROM giv_graphs_template WHERE default_tpl1 = '1' LIMIT 1"); @@ -1119,12 +1121,12 @@ public function setTemplate($template_id = null) /* * Graph is based on a module check point */ - $DBRESULT_meta = $this->DB->query( - "SELECT graph_id + $statement = $this->DB->prepare("SELECT graph_id FROM meta_service - WHERE `meta_name` = '" . $this->indexData["service_description"] . "'" - ); - $meta = $DBRESULT_meta->fetch(); + WHERE `meta_name` = :service_desc"); + $statement->bindValue(':service_desc', $this->indexData["service_description"], PDO::PARAM_STR); + $statement->execute(); + $meta = $statement->fetch(); $this->templateId = $meta["graph_id"]; unset($meta); } @@ -1149,14 +1151,14 @@ private function getServiceGraphID() $service_id = $this->indexData["service_id"]; $tab = array(); - while (1) { - $DBRESULT = $this->DB->query( - "SELECT esi.graph_id, service_template_model_stm_id + $statement = $this->DB->prepare("SELECT esi.graph_id, service_template_model_stm_id FROM service LEFT JOIN extended_service_information esi ON esi.service_service_id = service_id - WHERE service_id = '" . $service_id . "' LIMIT 1" - ); - $row = $DBRESULT->fetch(); + WHERE service_id = :service_id LIMIT 1"); + while (1) { + $statement->bindValue(':service_id', (int) $service_id, \PDO::PARAM_INT); + $statement->execute(); + $row = $statement->fetch(); if ($row["graph_id"]) { $this->graphID = $row["graph_id"]; return $this->graphID; From b524e4a228e6973935ce2e07b90c8955c0a1cbc9 Mon Sep 17 00:00:00 2001 From: Elmahdi ABBASSI <108519266+emabassi-ext@users.noreply.github.com> Date: Fri, 29 Jul 2022 11:53:08 +0100 Subject: [PATCH 12/18] [Snyk] Sanitize and bind ACL action access queries (#11385) (#11402) * Sanitize and bind ACL action access queries _ sanitize if possible each variables inserted in a query _ use PDO prepared statement and bind() method _ Do not use $pearDB->escape on which is for examples useless on integers and on non closed HTML tags (svg, img, etc) * fix line length * fix failed checks --- .../accessLists/menusACL/formMenusAccess.php | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/www/include/options/accessLists/menusACL/formMenusAccess.php b/www/include/options/accessLists/menusACL/formMenusAccess.php index 1939b23271c..6704e8b7ce8 100644 --- a/www/include/options/accessLists/menusACL/formMenusAccess.php +++ b/www/include/options/accessLists/menusACL/formMenusAccess.php @@ -209,9 +209,12 @@ $b = 0; $query = "SELECT topology_id, topology_page, topology_name, topology_parent, readonly FROM topology " . - "WHERE topology_parent = '" . $topo1["topology_page"] . "' ORDER BY topology_order"; - $DBRESULT2 = $pearDB->query($query); - while ($topo2 = $DBRESULT2->fetchRow()) { + "WHERE topology_parent = :topology_parent ORDER BY topology_order"; + + $statement2 = $pearDB->prepare($query); + $statement2->bindValue(':topology_parent', (int) $topo1["topology_page"], \PDO::PARAM_INT); + $statement2->execute(); + while ($topo2 = $statement2->fetchRow()) { $acl_topos2[$a]["childs"][$b] = array(); $acl_topos2[$a]["childs"][$b]["name"] = _($topo2["topology_name"]); $acl_topos2[$a]["childs"][$b]["id"] = $topo2["topology_id"]; @@ -231,10 +234,14 @@ $c = 0; $query = "SELECT topology_id, topology_name, topology_parent, topology_page, topology_group, readonly " . - "FROM topology WHERE topology_parent = '" . $topo2["topology_page"] . - "' AND topology_page IS NOT NULL ORDER BY topology_group, topology_order"; - $DBRESULT3 = $pearDB->query($query); - while ($topo3 = $DBRESULT3->fetchRow()) { + "FROM topology WHERE topology_parent = :topology_parent " . + "AND topology_page IS NOT NULL ORDER BY topology_group, topology_order"; + + $statement3 = $pearDB->prepare($query); + $statement3->bindValue(':topology_parent', (int) $topo2["topology_page"], \PDO::PARAM_INT); + $statement3->execute(); + + while ($topo3 = $statement3->fetchRow()) { $acl_topos2[$a]["childs"][$b]["childs"][$c] = array(); $acl_topos2[$a]["childs"][$b]["childs"][$c]["name"] = _($topo3["topology_name"]); @@ -264,10 +271,12 @@ $d = 0; $query = "SELECT topology_id, topology_name, topology_parent, readonly FROM topology " . - "WHERE topology_parent = '" . $topo3["topology_page"] . - "' AND topology_page IS NOT NULL ORDER BY topology_order"; - $DBRESULT4 = $pearDB->query($query); - while ($topo4 = $DBRESULT4->fetchRow()) { + "WHERE topology_parent = :topology_parent AND topology_page IS NOT NULL ORDER BY topology_order"; + $statement4 = $pearDB->prepare($query); + $statement4->bindValue(':topology_parent', (int) $topo3["topology_page"], \PDO::PARAM_INT); + $statement4->execute(); + + while ($topo4 = $statement4->fetchRow()) { $acl_topos2[$a]["childs"][$b]["childs"][$c]["childs"][$d] = array(); $acl_topos2[$a]["childs"][$b]["childs"][$c]["childs"][$d]["name"] = _($topo4["topology_name"]); $acl_topos2[$a]["childs"][$b]["childs"][$c]["childs"][$d]["id"] = $topo4["topology_id"]; From b04ce8a3c26cc9749ef1ba690f70939fe68f156d Mon Sep 17 00:00:00 2001 From: Kevin Duret Date: Fri, 29 Jul 2022 14:28:46 +0200 Subject: [PATCH 13/18] feat(api): implement endpoint to update centreon web (#11391) (#11401) Refs: MON-12296 --- ci/debian/centreon-web.postinst | 6 + composer.json | 1 + composer.lock | 83 ++++- config/packages/Centreon.yaml | 40 +++ config/routes/Centreon/platform.yaml | 6 + config/services.yaml | 5 + doc/API/centreon-api-v22.04.yaml | 2 + doc/API/v22.04/Administration/updates.yaml | 30 ++ lang/fr_FR.UTF-8/LC_MESSAGES/messages.po | 54 +++ .../Infrastructure/DatabaseConnection.php | 10 + .../Repository/AbstractRepositoryDRB.php | 4 +- .../ReadUpdateRepositoryInterface.php | 34 ++ .../ReadVersionRepositoryInterface.php | 33 ++ .../Repository/UpdateLockerException.php | 44 +++ .../UpdateLockerRepositoryInterface.php | 42 +++ .../Repository/UpdateNotFoundException.php | 36 ++ .../WriteUpdateRepositoryInterface.php | 40 +++ .../UseCase/UpdateVersions/UpdateVersions.php | 219 ++++++++++++ .../UpdateVersionsException.php | 87 +++++ .../UpdateVersionsPresenterInterface.php | 29 ++ .../Validator/RequirementException.php | 27 ++ .../RequirementValidatorInterface.php | 33 ++ .../RequirementValidatorsInterface.php | 33 ++ .../UpdateVersionsController.php | 70 ++++ .../UpdateVersionsPresenter.php | 31 ++ .../UpdateVersions/UpdateVersionsSchema.json | 25 ++ .../Repository/DbReadVersionRepository.php | 60 ++++ .../Repository/DbWriteUpdateRepository.php | 319 ++++++++++++++++++ .../Repository/FsReadUpdateRepository.php | 105 ++++++ .../SymfonyUpdateLockerRepository.php | 79 +++++ .../Validator/RequirementValidators.php | 63 ++++ .../DatabaseRequirementException.php | 49 +++ .../DatabaseRequirementValidator.php | 128 +++++++ .../DatabaseRequirementValidatorInterface.php | 43 +++ .../MariaDbRequirementException.php | 44 +++ .../MariaDbRequirementValidator.php | 82 +++++ .../PhpRequirementException.php | 56 +++ .../PhpRequirementValidator.php | 108 ++++++ tests/api/Context/PlatformUpdateContext.php | 48 +++ tests/api/behat.yml | 4 + tests/api/features/PlatformUpdate.feature | 41 +++ .../UpdateVersions/UpdateVersionsTest.php | 157 +++++++++ .../Repository/FsReadUpdateRepositoryTest.php | 89 +++++ tests/php/bootstrap.php | 3 +- .../step_upgrade/process/process_step4.php | 134 ++------ .../step_upgrade/process/process_step5.php | 58 +--- 46 files changed, 2546 insertions(+), 148 deletions(-) create mode 100644 doc/API/v22.04/Administration/updates.yaml create mode 100644 src/Core/Platform/Application/Repository/ReadUpdateRepositoryInterface.php create mode 100644 src/Core/Platform/Application/Repository/ReadVersionRepositoryInterface.php create mode 100644 src/Core/Platform/Application/Repository/UpdateLockerException.php create mode 100644 src/Core/Platform/Application/Repository/UpdateLockerRepositoryInterface.php create mode 100644 src/Core/Platform/Application/Repository/UpdateNotFoundException.php create mode 100644 src/Core/Platform/Application/Repository/WriteUpdateRepositoryInterface.php create mode 100644 src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersions.php create mode 100644 src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php create mode 100644 src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsPresenterInterface.php create mode 100644 src/Core/Platform/Application/Validator/RequirementException.php create mode 100644 src/Core/Platform/Application/Validator/RequirementValidatorInterface.php create mode 100644 src/Core/Platform/Application/Validator/RequirementValidatorsInterface.php create mode 100644 src/Core/Platform/Infrastructure/Api/UpdateVersions/UpdateVersionsController.php create mode 100644 src/Core/Platform/Infrastructure/Api/UpdateVersions/UpdateVersionsPresenter.php create mode 100644 src/Core/Platform/Infrastructure/Api/UpdateVersions/UpdateVersionsSchema.json create mode 100644 src/Core/Platform/Infrastructure/Repository/DbReadVersionRepository.php create mode 100644 src/Core/Platform/Infrastructure/Repository/DbWriteUpdateRepository.php create mode 100644 src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php create mode 100644 src/Core/Platform/Infrastructure/Repository/SymfonyUpdateLockerRepository.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementException.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidator.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidatorInterface.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidators/MariaDbRequirementException.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidators/MariaDbRequirementValidator.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/PhpRequirementException.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/PhpRequirementValidator.php create mode 100644 tests/api/Context/PlatformUpdateContext.php create mode 100644 tests/api/features/PlatformUpdate.feature create mode 100644 tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php create mode 100644 tests/php/Core/Platform/Infrastructure/Repository/FsReadUpdateRepositoryTest.php diff --git a/ci/debian/centreon-web.postinst b/ci/debian/centreon-web.postinst index 59f1d40c7ac..8a557942674 100644 --- a/ci/debian/centreon-web.postinst +++ b/ci/debian/centreon-web.postinst @@ -57,4 +57,10 @@ if [ "$1" = "configure" ] ; then fi fi + +# rebuild symfony cache on upgrade +if [ -n "$2" ]; then + su - www-data -s /bin/bash -c "/usr/share/centreon/bin/console cache:clear --no-warmup" +fi + exit 0 diff --git a/composer.json b/composer.json index c7c1a574bcd..e87c05a183d 100644 --- a/composer.json +++ b/composer.json @@ -65,6 +65,7 @@ "symfony/framework-bundle": "5.4.*", "symfony/http-client": "5.4.*", "symfony/http-kernel": "5.4.*", + "symfony/lock": "5.4.*", "symfony/maker-bundle": "^1.11", "symfony/monolog-bundle": "^3.7", "symfony/options-resolver": "5.4.*", diff --git a/composer.lock b/composer.lock index 23c9b05bd79..6dc53f71f39 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "94134d5a5dc2cb311e57863a9f0dafd8", + "content-hash": "1c4ccf2b3d1f768dfd30298637120a27", "packages": [ { "name": "beberlei/assert", @@ -3623,6 +3623,85 @@ ], "time": "2022-05-27T07:09:08+00:00" }, + { + "name": "symfony/lock", + "version": "v5.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/lock.git", + "reference": "41a308008d92d30cae5615d903c4d46d95932eea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/lock/zipball/41a308008d92d30cae5615d903c4d46d95932eea", + "reference": "41a308008d92d30cae5615d903c4d46d95932eea", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "doctrine/dbal": "<2.13" + }, + "require-dev": { + "doctrine/dbal": "^2.13|^3.0", + "predis/predis": "~1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Lock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jérémy Derussé", + "email": "jeremy@derusse.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Creates and manages locks, a mechanism to provide exclusive access to a shared resource", + "homepage": "https://symfony.com", + "keywords": [ + "cas", + "flock", + "locking", + "mutex", + "redlock", + "semaphore" + ], + "support": { + "source": "https://github.com/symfony/lock/tree/v5.4.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-09T13:29:56+00:00" + }, { "name": "symfony/maker-bundle", "version": "v1.43.0", @@ -10885,5 +10964,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.3.0" } diff --git a/config/packages/Centreon.yaml b/config/packages/Centreon.yaml index f0d99f90221..f8efbb150f4 100644 --- a/config/packages/Centreon.yaml +++ b/config/packages/Centreon.yaml @@ -223,6 +223,42 @@ services: class: Core\Infrastructure\Platform\Repository\FileReadPlatformRepository arguments: ['%centreon_etc_path%', '%centreon_install_path%'] + Core\Platform\Application\Validator\RequirementValidatorsInterface: + class: Core\Platform\Infrastructure\Validator\RequirementValidators + arguments: + $requirementValidators: !tagged_iterator 'platform.requirement.validators' + + Core\Platform\Infrastructure\Validator\RequirementValidators\DatabaseRequirementValidator: + arguments: + $dbRequirementValidators: !tagged_iterator 'platform.requirement.database.validators' + + Core\Platform\Infrastructure\Validator\RequirementValidators\PhpRequirementValidator: + arguments: + $requiredPhpVersion: '%required_php_version%' + + Core\Platform\Infrastructure\Validator\RequirementValidators\DatabaseRequirementValidators\MariaDbRequirementValidator: + arguments: + $requiredMariaDbMinVersion: '%required_mariadb_min_version%' + + Core\Platform\Application\Repository\ReadVersionRepositoryInterface: + class: Core\Platform\Infrastructure\Repository\DbReadVersionRepository + public: true + + Core\Platform\Application\Repository\ReadUpdateRepositoryInterface: + class: Core\Platform\Infrastructure\Repository\FsReadUpdateRepository + arguments: + $installDir: '%centreon_install_path%' + public: true + + Core\Platform\Application\Repository\UpdateLockerRepositoryInterface: + class: Core\Platform\Infrastructure\Repository\SymfonyUpdateLockerRepository + public: true + + Core\Platform\Application\Repository\WriteUpdateRepositoryInterface: + class: Core\Platform\Infrastructure\Repository\DbWriteUpdateRepository + arguments: ['%centreon_var_lib%', '%centreon_install_path%'] + public: true + # Monitoring resources _instanceof: Centreon\Infrastructure\Monitoring\Resource\Provider\ProviderInterface: @@ -238,6 +274,10 @@ services: tags: ['authentication.provider.responses'] Core\Security\Infrastructure\Api\FindProviderConfigurations\ProviderPresenter\ProviderPresenterInterface: tags: ['authentication.provider.presenters'] + Core\Platform\Application\Validator\RequirementValidatorInterface: + tags: ['platform.requirement.validators'] + Core\Platform\Infrastructure\Validator\RequirementValidators\DatabaseRequirementValidatorInterface: + tags: ['platform.requirement.database.validators'] Centreon\Domain\Monitoring\Interfaces\ResourceRepositoryInterface: factory: ['@Centreon\Infrastructure\Monitoring\Resource\ResourceRepositoryFactory', 'createResourceRepository'] diff --git a/config/routes/Centreon/platform.yaml b/config/routes/Centreon/platform.yaml index a521348ecbf..d77666e43f6 100644 --- a/config/routes/Centreon/platform.yaml +++ b/config/routes/Centreon/platform.yaml @@ -4,6 +4,12 @@ centreon_application_platform_getversion: controller: 'Centreon\Application\Controller\PlatformController::getVersions' condition: "request.attributes.get('version') >= 21.10" +centreon_application_platform_updateversions: + methods: PATCH + path: /platform/updates + controller: 'Core\Platform\Infrastructure\Api\UpdateVersions\UpdateVersionsController' + condition: "request.attributes.get('version') >= 22.04" + centreon_application_platformtopology_addplatformtotopology: methods: POST path: /platform/topology diff --git a/config/services.yaml b/config/services.yaml index 41975cd9de1..566596ebc51 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -22,6 +22,8 @@ parameters: media_path: "img/media" redirect_default_page: "/monitoring/resources" session_expiration_delay: 120 + required_php_version: "%env(_CENTREON_PHP_VERSION_)%" + required_mariadb_min_version: "%env(_CENTREON_MARIA_DB_MIN_VERSION_)%" services: # Default configuration for services in *this* file @@ -66,6 +68,9 @@ services: decorates: router arguments: ['@.inner'] + Symfony\Component\Finder\Finder: + shared: false + # Security Security\Domain\Authentication\Interfaces\AuthenticationRepositoryInterface: diff --git a/doc/API/centreon-api-v22.04.yaml b/doc/API/centreon-api-v22.04.yaml index a75b7a57821..bb949d8c36b 100644 --- a/doc/API/centreon-api-v22.04.yaml +++ b/doc/API/centreon-api-v22.04.yaml @@ -3528,6 +3528,8 @@ paths: moduleName: type: object $ref: '#/components/schemas/Platform.Versions' + /platform/updates: + $ref: "./v22.04/Administration/updates.yaml" /platform/installation/status: get: tags: diff --git a/doc/API/v22.04/Administration/updates.yaml b/doc/API/v22.04/Administration/updates.yaml new file mode 100644 index 00000000000..58e895903c1 --- /dev/null +++ b/doc/API/v22.04/Administration/updates.yaml @@ -0,0 +1,30 @@ +--- +patch: + tags: + - Platform + summary: "Update Centreon web" + description: | + Update Centreon web component + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + components: + type: array + items: + type: object + properties: + name: + type: string + enum: [ centreon-web ] + responses: + 204: + description: "Platform updated" + 404: + description: "Updates not found" + 500: + $ref: "../../centreon-api-v22.04.yaml#/components/responses/InternalServerError" +... \ No newline at end of file diff --git a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po index 1c1b0bbed83..73a58b0f385 100644 --- a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po +++ b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po @@ -16914,3 +16914,57 @@ msgstr "Les attributs liés suivants sont manquants : %s" msgid "Warning, maximum size exceeded for input '%s' (max: %d), it will be truncated upon saving" msgstr "Attention, taille maximale dépassée pour le champ '%s' (max: %d), il sera tronqué à l'enregistrement" + +msgid "Update already in progress" +msgstr "Une mise à jour est déjà en cours" + +msgid "An error occurred when retrieving the current version" +msgstr "Une erreur s'est produite lors de la récupération de la version actuelle" + +msgid "Cannot retrieve the current version" +msgstr "La version actuelle n'a pas pu être récupérée" + +msgid "An error occurred when retrieving available updates" +msgstr "Une erreur s'est produite lors de la récupération des mises à jour disponibles" + +msgid "An error occurred when applying the update %s (%s)" +msgstr "Une erreur s'est produite lors de l'application de la mise à jour %s (%s)" + +msgid "Error while locking the update process" +msgstr "Erreur lors du verrouillage du processus de mise à jour" + +msgid "Error while unlocking the update process" +msgstr "Erreur lors du déverrouillage du processus de mise à jour" + +msgid "An error occurred when applying post update actions" +msgstr "Une erreur s'est produite lors de l'application des actions postérieures à la mise à jour" + +msgid "Updates not found" +msgstr "Les mises à jour n'ont pas été trouvées" + +msgid "PHP version %s required (%s installed)" +msgstr "La version %s de PHP est requise (%s installée)" + +msgid "PHP extension %s not loaded" +msgstr "L'extension %s de PHP n'est pas chargée" + +msgid "Error when retrieving the database version" +msgstr "Erreur lors de la récupération de la version de la base de données" + +msgid "Cannot retrieve the database version information" +msgstr "Les informations de version de la base de données n'ont pas pu être récupérées" + +msgid "MariaDB version %s required (%s installed)" +msgstr "La version %s de MariaDB est requise (%s installée)" + +msgid "Service severity" +msgstr "Criticité du service" + +msgid "Service severity level" +msgstr "Niveau de criticité du service" + +msgid "Host severity" +msgstr "Criticité d'hôte" + +msgid "Host severity level" +msgstr "Niveau de criticité d'hôte" diff --git a/src/Centreon/Infrastructure/DatabaseConnection.php b/src/Centreon/Infrastructure/DatabaseConnection.php index 404ada96717..39263cc0cff 100644 --- a/src/Centreon/Infrastructure/DatabaseConnection.php +++ b/src/Centreon/Infrastructure/DatabaseConnection.php @@ -91,4 +91,14 @@ public function setStorageDbName(string $storageDbName) { $this->storageDbName = $storageDbName; } + + /** + * switch connection to another database + * + * @param string $dbName + */ + public function switchToDb(string $dbName): void + { + $this->query('use ' . $dbName); + } } diff --git a/src/Centreon/Infrastructure/Repository/AbstractRepositoryDRB.php b/src/Centreon/Infrastructure/Repository/AbstractRepositoryDRB.php index c8ccf79ea23..27e68c256fd 100644 --- a/src/Centreon/Infrastructure/Repository/AbstractRepositoryDRB.php +++ b/src/Centreon/Infrastructure/Repository/AbstractRepositoryDRB.php @@ -48,8 +48,8 @@ class AbstractRepositoryDRB protected function translateDbName(string $request): string { return str_replace( - array(':dbstg', ':db'), - array($this->db->getStorageDbName(), $this->db->getCentreonDbName()), + [':dbstg', ':db'], + [$this->db->getStorageDbName(), $this->db->getCentreonDbName()], $request ); } diff --git a/src/Core/Platform/Application/Repository/ReadUpdateRepositoryInterface.php b/src/Core/Platform/Application/Repository/ReadUpdateRepositoryInterface.php new file mode 100644 index 00000000000..db999e5fa71 --- /dev/null +++ b/src/Core/Platform/Application/Repository/ReadUpdateRepositoryInterface.php @@ -0,0 +1,34 @@ +info('Updating versions'); + + try { + $this->validateRequirementsOrFail(); + + $this->lockUpdate(); + + $currentVersion = $this->getCurrentVersionOrFail(); + + $availableUpdates = $this->getAvailableUpdatesOrFail($currentVersion); + + $this->runUpdates($availableUpdates); + + $this->unlockUpdate(); + + $this->runPostUpdate($this->getCurrentVersionOrFail()); + } catch (UpdateNotFoundException $e) { + $this->error( + $e->getMessage(), + ['trace' => $e->getTraceAsString()], + ); + + $presenter->setResponseStatus(new NotFoundResponse('Updates')); + + return; + } catch (\Throwable $e) { + $this->error( + $e->getMessage(), + ['trace' => $e->getTraceAsString()], + ); + + $presenter->setResponseStatus(new ErrorResponse($e->getMessage())); + + return; + } + + $presenter->setResponseStatus(new NoContentResponse()); + } + + /** + * Validate platform requirements or fail + * + * @throws \Exception + */ + private function validateRequirementsOrFail(): void + { + $this->info('Validating platform requirements'); + + $this->requirementValidators->validateRequirementsOrFail(); + } + + /** + * Lock update process + */ + private function lockUpdate(): void + { + $this->info('Locking centreon update process...'); + + if (!$this->updateLocker->lock()) { + throw UpdateVersionsException::updateAlreadyInProgress(); + } + } + + /** + * Unlock update process + */ + private function unlockUpdate(): void + { + $this->info('Unlocking centreon update process...'); + + $this->updateLocker->unlock(); + } + + /** + * Get current version or fail + * + * @return string + * + * @throws \Exception + */ + private function getCurrentVersionOrFail(): string + { + $this->info('Getting current version'); + + try { + $currentVersion = $this->readVersionRepository->findCurrentVersion(); + } catch (\Exception $e) { + throw UpdateVersionsException::errorWhenRetrievingCurrentVersion($e); + } + + if ($currentVersion === null) { + throw UpdateVersionsException::cannotRetrieveCurrentVersion(); + } + + return $currentVersion; + } + + /** + * Get available updates + * + * @param string $currentVersion + * @return string[] + */ + private function getAvailableUpdatesOrFail(string $currentVersion): array + { + try { + $this->info( + 'Getting available updates', + [ + 'current_version' => $currentVersion, + ], + ); + + return $this->readUpdateRepository->findOrderedAvailableUpdates($currentVersion); + } catch (UpdateNotFoundException $e) { + throw $e; + } catch (\Throwable $e) { + throw UpdateVersionsException::errorWhenRetrievingAvailableUpdates($e); + } + } + + /** + * Run given version updates + * + * @param string[] $versions + * + * @throws \Throwable + */ + private function runUpdates(array $versions): void + { + foreach ($versions as $version) { + try { + $this->info("Running update $version"); + $this->writeUpdateRepository->runUpdate($version); + } catch (\Throwable $e) { + throw UpdateVersionsException::errorWhenApplyingUpdate($version, $e->getMessage(), $e); + } + } + } + + /** + * Run post update actions + * + * @param string $currentVersion + * + * @throws UpdateVersionsException + */ + private function runPostUpdate(string $currentVersion): void + { + $this->info("Running post update actions"); + + try { + $this->writeUpdateRepository->runPostUpdate($currentVersion); + } catch (\Throwable $e) { + throw UpdateVersionsException::errorWhenApplyingPostUpdate($e); + } + } +} diff --git a/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php new file mode 100644 index 00000000000..dbfaec97ba3 --- /dev/null +++ b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php @@ -0,0 +1,87 @@ +denyAccessUnlessGrantedForApiConfiguration(); + + /** + * @var Contact $contact + */ + $contact = $this->getUser(); + if (! $contact->isAdmin()) { + $presenter->setResponseStatus(new UnauthorizedResponse('Only admin user can perform upgrade')); + + return $presenter->show(); + } + + $this->info('Validating request body...'); + $this->validateDataSent($request, __DIR__ . '/UpdateVersionsSchema.json'); + + $useCase($presenter); + + return $presenter->show(); + } +} diff --git a/src/Core/Platform/Infrastructure/Api/UpdateVersions/UpdateVersionsPresenter.php b/src/Core/Platform/Infrastructure/Api/UpdateVersions/UpdateVersionsPresenter.php new file mode 100644 index 00000000000..a27dcfad745 --- /dev/null +++ b/src/Core/Platform/Infrastructure/Api/UpdateVersions/UpdateVersionsPresenter.php @@ -0,0 +1,31 @@ +db = $db; + } + + /** + * @inheritDoc + */ + public function findCurrentVersion(): ?string + { + $currentVersion = null; + + $statement = $this->db->query( + "SELECT `value` FROM `informations` WHERE `key` = 'version'" + ); + if ($statement !== false && is_array($result = $statement->fetch(\PDO::FETCH_ASSOC))) { + $currentVersion = $result['value']; + } + + return $currentVersion; + } +} diff --git a/src/Core/Platform/Infrastructure/Repository/DbWriteUpdateRepository.php b/src/Core/Platform/Infrastructure/Repository/DbWriteUpdateRepository.php new file mode 100644 index 00000000000..1255ee9ecc8 --- /dev/null +++ b/src/Core/Platform/Infrastructure/Repository/DbWriteUpdateRepository.php @@ -0,0 +1,319 @@ +db = $db; + } + + /** + * @inheritDoc + */ + public function runUpdate(string $version): void + { + $this->runMonitoringSql($version); + $this->runScript($version); + $this->runConfigurationSql($version); + $this->runPostScript($version); + $this->updateVersionInformation($version); + } + + /** + * @inheritDoc + */ + public function runPostUpdate(string $currentVersion): void + { + if (! $this->filesystem->exists($this->installDir)) { + return; + } + + $this->backupInstallDirectory($currentVersion); + $this->removeInstallDirectory(); + } + + /** + * Backup installation directory + * + * @param string $currentVersion + */ + private function backupInstallDirectory(string $currentVersion): void + { + $backupDirectory = $this->libDir . '/installs/install-' . $currentVersion . '-' . date('Ymd_His'); + + $this->info( + "Backing up installation directory", + [ + 'source' => $this->installDir, + 'destination' => $backupDirectory, + ], + ); + + $this->filesystem->mirror( + $this->installDir, + $backupDirectory, + ); + } + + /** + * Remove installation directory + */ + private function removeInstallDirectory(): void + { + $this->info( + "Removing installation directory", + [ + 'installation_directory' => $this->installDir, + ], + ); + + $this->filesystem->remove($this->installDir); + } + + /** + * Run sql queries on monitoring database + * + * @param string $version + */ + private function runMonitoringSql(string $version): void + { + $upgradeFilePath = $this->installDir . '/sql/centstorage/Update-CSTG-' . $version . '.sql'; + if (is_readable($upgradeFilePath)) { + $this->db->switchToDb($this->db->getStorageDbName()); + $this->runSqlFile($upgradeFilePath); + } + } + + /** + * Run php upgrade script + * + * @param string $version + */ + private function runScript(string $version): void + { + $pearDB = $this->dependencyInjector['configuration_db']; + $pearDBO = $this->dependencyInjector['realtime_db']; + + $upgradeFilePath = $this->installDir . '/php/Update-' . $version . '.php'; + if (is_readable($upgradeFilePath)) { + include_once $upgradeFilePath; + } + } + + /** + * Run sql queries on configuration database + * + * @param string $version + */ + private function runConfigurationSql(string $version): void + { + $upgradeFilePath = $this->installDir . '/sql/centreon/Update-DB-' . $version . '.sql'; + if (is_readable($upgradeFilePath)) { + $this->db->switchToDb($this->db->getCentreonDbName()); + $this->runSqlFile($upgradeFilePath); + } + } + + /** + * Run php post upgrade script + * + * @param string $version + */ + private function runPostScript(string $version): void + { + $pearDB = $this->dependencyInjector['configuration_db']; + $pearDBO = $this->dependencyInjector['realtime_db']; + + $upgradeFilePath = $this->installDir . '/php/Update-' . $version . '.post.php'; + if (is_readable($upgradeFilePath)) { + include_once $upgradeFilePath; + } + } + + /** + * Update version information + * + * @param string $version + */ + private function updateVersionInformation(string $version): void + { + $statement = $this->db->prepare( + $this->translateDbName( + "UPDATE `:db`.`informations` SET `value` = :version WHERE `key` = 'version'" + ) + ); + $statement->bindValue(':version', $version, \PDO::PARAM_STR); + $statement->execute(); + } + + /** + * Run sql file and use temporary file to store last executed line + * + * @param string $filePath + * @return void + */ + private function runSqlFile(string $filePath): void + { + set_time_limit(0); + + $fileName = basename($filePath); + $tmpFile = $this->installDir . '/tmp/' . $fileName; + + $alreadyExecutedQueriesCount = $this->getAlreadyExecutedQueriesCount($tmpFile); + + if (is_readable($filePath)) { + $fileStream = fopen($filePath, 'r'); + if (is_resource($fileStream)) { + $query = ''; + $currentLineNumber = 0; + $executedQueriesCount = 0; + try { + while (! feof($fileStream)) { + $currentLineNumber++; + $currentLine = fgets($fileStream); + if ($currentLine && ! $this->isSqlComment($currentLine)) { + $query .= ' ' . trim($currentLine); + } + + if ($this->isSqlCompleteQuery($query)) { + $executedQueriesCount++; + if ($executedQueriesCount > $alreadyExecutedQueriesCount) { + try { + $this->executeQuery($query); + } catch (RepositoryException $e) { + throw $e; + } + + $this->writeExecutedQueriesCountInTemporaryFile($tmpFile, $executedQueriesCount); + } + $query = ''; + } + } + } catch (\Throwable $e) { + $this->error($e->getMessage(), ['trace' => $e->getTraceAsString()]); + throw $e; + } finally { + fclose($fileStream); + } + } + } + } + + /** + * Get stored executed queries count in temporary file to retrieve next query to run in case of an error occurred + * + * @param string $tmpFile + * @return int + */ + private function getAlreadyExecutedQueriesCount(string $tmpFile): int + { + $startLineNumber = 0; + if (is_readable($tmpFile)) { + $lineNumber = file_get_contents($tmpFile); + if (is_numeric($lineNumber)) { + $startLineNumber = (int) $lineNumber; + } + } + + return $startLineNumber; + } + + /** + * Write executed queries count in temporary file to retrieve upgrade when an error occurred + * + * @param string $tmpFile + * @param int $count + */ + private function writeExecutedQueriesCountInTemporaryFile(string $tmpFile, int $count): void + { + if (! file_exists($tmpFile) || is_writable($tmpFile)) { + $this->info('Writing in temporary file : ' . $tmpFile); + file_put_contents($tmpFile, $count); + } else { + $this->warning('Cannot write in temporary file : ' . $tmpFile); + } + } + + /** + * Check if a line a sql comment + * + * @param string $line + * @return bool + */ + private function isSqlComment(string $line): bool + { + return str_starts_with('--', trim($line)); + } + + /** + * Check if a query is complete (trailing semicolon) + * + * @param string $query + * @return bool + */ + private function isSqlCompleteQuery(string $query): bool + { + return ! empty(trim($query)) && preg_match('/;\s*$/', $query); + } + + /** + * Execute sql query + * + * @param string $query + * + * @throws \Exception + */ + private function executeQuery(string $query): void + { + try { + $this->db->query($query); + } catch (\Exception $e) { + throw new RepositoryException('Cannot execute query: ' . $query, 0, $e); + } + } +} diff --git a/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php b/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php new file mode 100644 index 00000000000..8c0a7916698 --- /dev/null +++ b/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php @@ -0,0 +1,105 @@ +findAvailableUpdates($currentVersion); + + return $this->orderUpdates($availableUpdates); + } + + /** + * Get available updates + * + * @param string $currentVersion + * @return string[] + */ + private function findAvailableUpdates(string $currentVersion): array + { + if (! $this->filesystem->exists($this->installDir)) { + $this->error('Install directory not found on filesystem: ' . $this->installDir); + throw UpdateNotFoundException::updatesNotFound(); + } + + $fileNameVersionRegex = '/Update-(?[a-zA-Z0-9\-\.]+)\.php/'; + $availableUpdates = []; + + $updateFiles = $this->finder->files() + ->in($this->installDir) + ->name($fileNameVersionRegex); + + foreach ($updateFiles as $updateFile) { + if (preg_match($fileNameVersionRegex, $updateFile->getFilename(), $matches)) { + if (version_compare($matches['version'], $currentVersion, '>')) { + $this->info('Update version found: ' . $matches['version']); + $availableUpdates[] = $matches['version']; + } + } + } + + return $availableUpdates; + } + + /** + * Order updates + * + * @param string[] $updates + * @return string[] + */ + private function orderUpdates(array $updates): array + { + usort( + $updates, + fn (string $versionA, string $versionB) => version_compare($versionA, $versionB), + ); + + return $updates; + } +} diff --git a/src/Core/Platform/Infrastructure/Repository/SymfonyUpdateLockerRepository.php b/src/Core/Platform/Infrastructure/Repository/SymfonyUpdateLockerRepository.php new file mode 100644 index 00000000000..2442b6c0e0b --- /dev/null +++ b/src/Core/Platform/Infrastructure/Repository/SymfonyUpdateLockerRepository.php @@ -0,0 +1,79 @@ +lock = $lockFactory->createLock(self::LOCK_NAME); + } + + /** + * @inheritDoc + */ + public function lock(): bool + { + $this->info('Locking centreon update process on filesystem...'); + + try { + return $this->lock->acquire(); + } catch (\Throwable $e) { + throw UpdateLockerException::errorWhileLockingUpdate($e); + } + } + + /** + * @inheritDoc + */ + public function unlock(): void + { + $this->info('Unlocking centreon update process from filesystem...'); + + try { + $this->lock->release(); + } catch (\Throwable $e) { + throw UpdateLockerException::errorWhileUnlockingUpdate($e); + } + } +} diff --git a/src/Core/Platform/Infrastructure/Validator/RequirementValidators.php b/src/Core/Platform/Infrastructure/Validator/RequirementValidators.php new file mode 100644 index 00000000000..b573a47a62d --- /dev/null +++ b/src/Core/Platform/Infrastructure/Validator/RequirementValidators.php @@ -0,0 +1,63 @@ + $requirementValidators + * + * @throws \Exception + */ + public function __construct( + \Traversable $requirementValidators, + ) { + if (iterator_count($requirementValidators) === 0) { + throw new \Exception('Requirement validators not found'); + } + $this->requirementValidators = iterator_to_array($requirementValidators); + } + + /** + * @inheritDoc + */ + public function validateRequirementsOrFail(): void + { + foreach ($this->requirementValidators as $requirementValidator) { + $this->info('Validating platform requirement with ' . $requirementValidator::class); + $requirementValidator->validateRequirementOrFail(); + } + } +} diff --git a/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementException.php b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementException.php new file mode 100644 index 00000000000..f9517d8216a --- /dev/null +++ b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementException.php @@ -0,0 +1,49 @@ + $dbRequirementValidators + * + * @throws \Exception + */ + public function __construct( + DatabaseConnection $db, + \Traversable $dbRequirementValidators, + ) { + $this->db = $db; + + if (iterator_count($dbRequirementValidators) === 0) { + throw new \Exception('Database requirement validators not found'); + } + $this->dbRequirementValidators = iterator_to_array($dbRequirementValidators); + } + + /** + * {@inheritDoc} + * + * @throws DatabaseRequirementException + */ + public function validateRequirementOrFail(): void + { + $this->initDatabaseVersionInformation(); + + foreach ($this->dbRequirementValidators as $dbRequirementValidator) { + if ($dbRequirementValidator->isValidFor($this->versionComment)) { + $this->info( + 'Validating requirement by ' . $dbRequirementValidator::class, + [ + 'current_version' => $this->version, + ], + ); + $dbRequirementValidator->validateRequirementOrFail($this->version); + $this->info('Requirement validated by ' . $dbRequirementValidator::class); + } + } + } + + /** + * Get database version information + * + * @throws DatabaseRequirementException + */ + private function initDatabaseVersionInformation(): void + { + $this->info('Getting database version information'); + + try { + $statement = $this->db->query("SHOW VARIABLES WHERE Variable_name IN ('version', 'version_comment')"); + while ($statement !== false && is_array($row = $statement->fetch(\PDO::FETCH_ASSOC))) { + if ($row['Variable_name'] === "version") { + $this->info('Retrieved DBMS version: ' . $row['Value']); + $this->version = $row['Value']; + } elseif ($row['Variable_name'] === "version_comment") { + $this->info('Retrieved DBMS version comment: ' . $row['Value']); + $this->versionComment = $row['Value']; + } + } + } catch (\Throwable $e) { + $this->error( + 'Error when getting DBMS version from database', + [ + 'message' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ], + ); + throw DatabaseRequirementException::errorWhenGettingDatabaseVersion($e); + } + + if (empty($this->version) || empty($this->versionComment)) { + $this->info('Cannot retrieve the database version information'); + throw DatabaseRequirementException::cannotRetrieveVersionInformation(); + } + } +} diff --git a/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidatorInterface.php b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidatorInterface.php new file mode 100644 index 00000000000..ba44860931b --- /dev/null +++ b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidatorInterface.php @@ -0,0 +1,43 @@ +info( + 'Checking if version comment contains MariaDB string', + [ + 'version_comment' => $versionComment, + ], + ); + + return strpos($versionComment, "MariaDB") !== false; + } + + /** + * {@inheritDoc} + * + * @throws MariaDbRequirementException + */ + public function validateRequirementOrFail(string $version): void + { + $currentMariaDBMajorVersion = VersionHelper::regularizeDepthVersion($version, 1); + + $this->info( + 'Comparing current MariaDB version ' . $currentMariaDBMajorVersion + . ' to minimal required version ' . $this->requiredMariaDbMinVersion + ); + + if ( + VersionHelper::compare($currentMariaDBMajorVersion, $this->requiredMariaDbMinVersion, VersionHelper::LT) + ) { + $this->error('MariaDB requirement is not validated'); + + throw MariaDbRequirementException::badMariaDbVersion( + $this->requiredMariaDbMinVersion, + $currentMariaDBMajorVersion, + ); + } + } +} diff --git a/src/Core/Platform/Infrastructure/Validator/RequirementValidators/PhpRequirementException.php b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/PhpRequirementException.php new file mode 100644 index 00000000000..8086d3d86bc --- /dev/null +++ b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/PhpRequirementException.php @@ -0,0 +1,56 @@ +validatePhpVersionOrFail(); + $this->validatePhpExtensionsOrFail(); + } + + /** + * Check installed php version + * + * @throws PhpRequirementException + */ + private function validatePhpVersionOrFail(): void + { + $currentPhpMajorVersion = VersionHelper::regularizeDepthVersion(PHP_VERSION, 1); + + $this->info( + 'Comparing current PHP version ' . $currentPhpMajorVersion + . ' to required version ' . $this->requiredPhpVersion + ); + if (! VersionHelper::compare($currentPhpMajorVersion, $this->requiredPhpVersion, VersionHelper::EQUAL)) { + throw PhpRequirementException::badPhpVersion($this->requiredPhpVersion, $currentPhpMajorVersion); + } + } + + /** + * Check if required php extensions are loaded + * + * @throws PhpRequirementException + */ + private function validatePhpExtensionsOrFail(): void + { + $this->info('Checking PHP extensions'); + foreach (self::EXTENSION_REQUIREMENTS as $extensionName) { + $this->validatePhpExtensionOrFail($extensionName); + } + } + + /** + * check if given php extension is loaded + * + * @param string $extensionName + * + * @throws PhpRequirementException + */ + private function validatePhpExtensionOrFail(string $extensionName): void + { + $this->info('Checking PHP extension ' . $extensionName); + if (! extension_loaded($extensionName)) { + $this->error('PHP extension ' . $extensionName . ' is not loaded'); + throw PhpRequirementException::phpExtensionNotLoaded($extensionName); + } + } +} diff --git a/tests/api/Context/PlatformUpdateContext.php b/tests/api/Context/PlatformUpdateContext.php new file mode 100644 index 00000000000..842bd5c0379 --- /dev/null +++ b/tests/api/Context/PlatformUpdateContext.php @@ -0,0 +1,48 @@ +getContainer()->execute( + 'mkdir -p /usr/share/centreon/www/install/php', + 'web' + ); + $this->getContainer()->execute( + "sh -c 'echo \" /usr/share/centreon/www/install/php/Update-99.99.99.php'", + 'web' + ); + $this->getContainer()->execute( + 'chown -R apache. /usr/share/centreon/www/install', + 'web' + ); + } +} diff --git a/tests/api/behat.yml b/tests/api/behat.yml index be4954bf1ae..201f97cd472 100644 --- a/tests/api/behat.yml +++ b/tests/api/behat.yml @@ -72,6 +72,10 @@ default: paths: [ "%paths.base%/features/PlatformInformation.feature" ] contexts: - Centreon\Test\Api\Context\PlatformInformationContext + platform_update: + paths: [ "%paths.base%/features/PlatformUpdate.feature" ] + contexts: + - Centreon\Test\Api\Context\PlatformUpdateContext host_groups: paths: [ "%paths.base%/features/HostGroup.feature" ] contexts: diff --git a/tests/api/features/PlatformUpdate.feature b/tests/api/features/PlatformUpdate.feature new file mode 100644 index 00000000000..624d875699d --- /dev/null +++ b/tests/api/features/PlatformUpdate.feature @@ -0,0 +1,41 @@ +Feature: + In order to maintain easily centreon platform + As a user + I want to update centreon web using api + + Background: + Given a running instance of Centreon Web API + And the endpoints are described in Centreon Web API documentation + + Scenario: Update platform information + Given I am logged in + + When an update is available + And I send a PATCH request to '/api/latest/platform/updates' with body: + """ + { + "components": [ + { + "name": "centreon-web" + } + ] + } + """ + Then the response code should be "204" + + When I send a GET request to '/api/latest/platform/versions' + Then the response code should be "200" + And the JSON node "web.version" should be equal to the string "99.99.99" + + When I send a PATCH request to '/api/latest/platform/updates' with body: + """ + { + "components": [ + { + "name": "centreon-web" + } + ] + } + """ + Then the response code should be "404" + And the JSON node "message" should be equal to the string "Updates not found" \ No newline at end of file diff --git a/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php b/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php new file mode 100644 index 00000000000..6f96a95531a --- /dev/null +++ b/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php @@ -0,0 +1,157 @@ +requirementValidators = $this->createMock(RequirementValidatorsInterface::class); + $this->updateLockerRepository = $this->createMock(UpdateLockerRepositoryInterface::class); + $this->readVersionRepository = $this->createMock(ReadVersionRepositoryInterface::class); + $this->readUpdateRepository = $this->createMock(ReadUpdateRepositoryInterface::class); + $this->writeUpdateRepository = $this->createMock(WriteUpdateRepositoryInterface::class); + $this->presenter = $this->createMock(UpdateVersionsPresenterInterface::class); +}); + +it('should stop update process when an other update is already started', function () { + $updateVersions = new UpdateVersions( + $this->requirementValidators, + $this->updateLockerRepository, + $this->readVersionRepository, + $this->readUpdateRepository, + $this->writeUpdateRepository, + ); + + $this->updateLockerRepository + ->expects($this->once()) + ->method('lock') + ->willReturn(false); + + $this->presenter + ->expects($this->once()) + ->method('setResponseStatus') + ->with(new ErrorResponse('Update already in progress')); + + $updateVersions($this->presenter); +}); + +it('should present an error response if a requirement is not validated', function () { + $updateVersions = new UpdateVersions( + $this->requirementValidators, + $this->updateLockerRepository, + $this->readVersionRepository, + $this->readUpdateRepository, + $this->writeUpdateRepository, + ); + + $this->requirementValidators + ->expects($this->once()) + ->method('validateRequirementsOrFail') + ->willThrowException(new RequirementException('Requirement is not validated')); + + $this->presenter + ->expects($this->once()) + ->method('setResponseStatus') + ->with(new ErrorResponse('Requirement is not validated')); + + $updateVersions($this->presenter); +}); + +it('should present an error response if current centreon version is not found', function () { + $updateVersions = new UpdateVersions( + $this->requirementValidators, + $this->updateLockerRepository, + $this->readVersionRepository, + $this->readUpdateRepository, + $this->writeUpdateRepository, + ); + + $this->updateLockerRepository + ->expects($this->once()) + ->method('lock') + ->willReturn(true); + + $this->readVersionRepository + ->expects($this->once()) + ->method('findCurrentVersion') + ->willReturn(null); + + $this->presenter + ->expects($this->once()) + ->method('setResponseStatus') + ->with(new ErrorResponse('Cannot retrieve the current version')); + + $updateVersions($this->presenter); +}); + +it('should run found updates', function () { + $updateVersions = new UpdateVersions( + $this->requirementValidators, + $this->updateLockerRepository, + $this->readVersionRepository, + $this->readUpdateRepository, + $this->writeUpdateRepository, + ); + + $this->updateLockerRepository + ->expects($this->once()) + ->method('lock') + ->willReturn(true); + + $this->readVersionRepository + ->expects($this->exactly(2)) + ->method('findCurrentVersion') + ->will($this->onConsecutiveCalls('22.04.0', '22.10.1')); + + $this->readUpdateRepository + ->expects($this->once()) + ->method('findOrderedAvailableUpdates') + ->with('22.04.0') + ->willReturn(['22.10.0-beta.1', '22.10.0', '22.10.1']); + + $this->writeUpdateRepository + ->expects($this->exactly(3)) + ->method('runUpdate') + ->withConsecutive( + [$this->equalTo('22.10.0-beta.1')], + [$this->equalTo('22.10.0')], + [$this->equalTo('22.10.1')], + ); + + $this->presenter + ->expects($this->once()) + ->method('setResponseStatus') + ->with(new NoContentResponse()); + + $updateVersions($this->presenter); +}); diff --git a/tests/php/Core/Platform/Infrastructure/Repository/FsReadUpdateRepositoryTest.php b/tests/php/Core/Platform/Infrastructure/Repository/FsReadUpdateRepositoryTest.php new file mode 100644 index 00000000000..16682e3ea3c --- /dev/null +++ b/tests/php/Core/Platform/Infrastructure/Repository/FsReadUpdateRepositoryTest.php @@ -0,0 +1,89 @@ +filesystem = $this->createMock(Filesystem::class); + $this->finder = $this->createMock(Finder::class); +}); + +it('should return an error when install directory does not exist', function () { + $repository = new FsReadUpdateRepository(sys_get_temp_dir(), $this->filesystem, $this->finder); + + $this->filesystem + ->expects($this->once()) + ->method('exists') + ->willReturn(false); + + $availableUpdates = $repository->findOrderedAvailableUpdates('22.04.0'); +})->throws( + UpdateNotFoundException::class, + UpdateNotFoundException::updatesNotFound()->getMessage(), +); + +it('should order found updates', function () { + $repository = new FsReadUpdateRepository(sys_get_temp_dir(), $this->filesystem, $this->finder); + + $this->filesystem + ->expects($this->once()) + ->method('exists') + ->willReturn(true); + + $this->finder + ->expects($this->once()) + ->method('files') + ->willReturn($this->finder); + + $this->finder + ->expects($this->once()) + ->method('in') + ->willReturn($this->finder); + + $this->finder + ->expects($this->once()) + ->method('name') + ->willReturn( + [ + new \SplFileInfo('Update-21.10.0.php'), + new \SplFileInfo('Update-22.04.0.php'), + new \SplFileInfo('Update-22.10.11.php'), + new \SplFileInfo('Update-22.10.1.php'), + new \SplFileInfo('Update-22.10.0-beta.3.php'), + new \SplFileInfo('Update-22.10.0-alpha.1.php'), + ] + ); + + $availableUpdates = $repository->findOrderedAvailableUpdates('22.04.0'); + expect($availableUpdates)->toEqual([ + '22.10.0-alpha.1', + '22.10.0-beta.3', + '22.10.1', + '22.10.11' + ]); +}); diff --git a/tests/php/bootstrap.php b/tests/php/bootstrap.php index 2ca3e800b03..d1d41179b9f 100644 --- a/tests/php/bootstrap.php +++ b/tests/php/bootstrap.php @@ -24,8 +24,7 @@ } $mockedPreRequisiteConstants = [ - '_CENTREON_PHP_MIN_VERSION_' => '8.0', - '_CENTREON_PHP_MAX_VERSION_' => '8.0', + '_CENTREON_PHP_VERSION_' => '8.0', '_CENTREON_MARIA_DB_MIN_VERSION_' => '10.5', ]; foreach ($mockedPreRequisiteConstants as $mockedPreRequisiteConstant => $value) { diff --git a/www/install/step_upgrade/process/process_step4.php b/www/install/step_upgrade/process/process_step4.php index 308c61a21af..95b771fc5de 100644 --- a/www/install/step_upgrade/process/process_step4.php +++ b/www/install/step_upgrade/process/process_step4.php @@ -1,128 +1,62 @@ . + * http://www.apache.org/licenses/LICENSE-2.0 * - * Linking this program statically or dynamically with other modules is making a - * combined work based on this program. Thus, the terms and conditions of the GNU - * General Public License cover the whole combination. - * - * As a special exception, the copyright holders of this program give Centreon - * permission to link this program with independent modules to produce an executable, - * regardless of the license terms of these independent modules, and to copy and - * distribute the resulting executable under terms of Centreon choice, provided that - * Centreon also meet, for each linked independent module, the terms and conditions - * of the license of that module. An independent module is a module which is not - * derived from this program. If you modify this program, you may extend this - * exception to your version of the program, but you are not obliged to do so. If you - * do not wish to do so, delete this exception statement from your version. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. * * For more information : contact@centreon.com * */ session_start(); -require_once realpath(dirname(__FILE__) . "/../../../../config/centreon.config.php"); -require_once _CENTREON_PATH_ . '/www/class/centreonDB.class.php'; -require_once '../../steps/functions.php'; +require_once __DIR__ . '/../../../../bootstrap.php'; +require_once __DIR__ . '/../../../class/centreonDB.class.php'; +require_once __DIR__ . '/../../steps/functions.php'; + +use Core\Platform\Application\Repository\UpdateLockerRepositoryInterface; +use Core\Platform\Application\Repository\ReadUpdateRepositoryInterface; +use Core\Platform\Application\Repository\WriteUpdateRepositoryInterface; +use Core\Platform\Application\UseCase\UpdateVersions\UpdateVersionsException; $current = $_POST['current']; $next = $_POST['next']; $status = 0; -/** - * Variables for upgrade scripts - */ -try { - $pearDB = new CentreonDB('centreon', 3); - $pearDBO = new CentreonDB('centstorage', 3); -} catch (Exception $e) { - exitUpgradeProcess(1, $current, $next, $e->getMessage()); -} +$kernel = \App\Kernel::createForWeb(); -/** - * Upgrade storage sql - */ -$storageSql = '../../sql/centstorage/Update-CSTG-' . $next . '.sql'; -if (is_file($storageSql)) { - $result = splitQueries($storageSql, ';', $pearDBO, '../../tmp/Update-CSTG-' . $next); - if ("0" != $result) { - exitUpgradeProcess(1, $current, $next, $result); - } -} +$updateLockerRepository = $kernel->getContainer()->get(UpdateLockerRepositoryInterface::class); +$updateWriteRepository = $kernel->getContainer()->get(WriteUpdateRepositoryInterface::class); -/** - * Pre upgrade PHP - */ -$prePhp = '../../php/Update-' . $next . '.php'; -if (is_file($prePhp)) { - try { - include_once $prePhp; - } catch (Exception $e) { - exitUpgradeProcess(1, $current, $next, $e->getMessage()); +try { + if (! $updateLockerRepository->lock()) { + throw UpdateVersionsException::updateAlreadyInProgress(); } -} -/** - * Upgrade configuration sql - */ -$confSql = '../../sql/centreon/Update-DB-' . $next . '.sql'; -if (is_file($confSql)) { - $result = splitQueries($confSql, ';', $pearDB, '../../tmp/Update-DB-' . $next); - if ("0" != $result) { - exitUpgradeProcess(1, $current, $next, $result); - } -} + $updateWriteRepository->runUpdate($next); -/** - * Post upgrade PHP - */ -$postPhp = '../../php/Update-' . $next . '.post.php'; -if (is_file($postPhp)) { - try { - include_once $postPhp; - } catch (Exception $e) { - exitUpgradeProcess(1, $current, $next, $e->getMessage()); - } + $updateLockerRepository->unlock(); +} catch (\Throwable $e) { + exitUpgradeProcess(1, $current, $next, $e->getMessage()); } -/** - * Update version in database. - */ -$res = $pearDB->prepare("UPDATE `informations` SET `value` = ? WHERE `key` = 'version'"); -$res->execute(array($next)); $current = $next; -/* -** To find the next version that we should update to, we will look in -** the www/install/php directory where all PHP update scripts are -** stored. We will extract the target version from the filename and find -** the closest version to the current version. -*/ -$next = ''; -if ($handle = opendir('../../php')) { - while (false !== ($file = readdir($handle))) { - if (preg_match('/Update-([a-zA-Z0-9\-\.]+)\.php/', $file, $matches)) { - if ((version_compare($current, $matches[1]) < 0) && - (empty($next) || (version_compare($matches[1], $next) < 0))) { - $next = $matches[1]; - } - } - } - closedir($handle); -} +$updateReadRepository = $kernel->getContainer()->get(ReadUpdateRepositoryInterface::class); +$availableUpdates = $updateReadRepository->findOrderedAvailableUpdates($current); +$next = empty($availableUpdates) ? '' : array_shift($availableUpdates); + $_SESSION['CURRENT_VERSION'] = $current; $okMsg = "OK"; + exitUpgradeProcess($status, $current, $next, $okMsg); diff --git a/www/install/step_upgrade/process/process_step5.php b/www/install/step_upgrade/process/process_step5.php index df5d79e2174..c4a723a14f2 100644 --- a/www/install/step_upgrade/process/process_step5.php +++ b/www/install/step_upgrade/process/process_step5.php @@ -37,44 +37,15 @@ require_once __DIR__ . '/../../../../bootstrap.php'; require_once '../../steps/functions.php'; -function recurseRmdir($dir) -{ - $files = array_diff(scandir($dir), array('.', '..')); - foreach ($files as $file) { - (is_dir("$dir/$file")) ? recurseRmdir("$dir/$file") : unlink("$dir/$file"); - } - return rmdir($dir); -} - -function recurseCopy($source, $dest) -{ - if (is_link($source)) { - return symlink(readlink($source), $dest); - } +use Core\Platform\Application\Repository\WriteUpdateRepositoryInterface; +use Core\Platform\Application\UseCase\UpdateVersions\UpdateVersionsException; - if (is_file($source)) { - return copy($source, $dest); - } - - if (!is_dir($dest)) { - mkdir($dest); - } +$kernel = \App\Kernel::createForWeb(); - $dir = dir($source); - while (false !== $entry = $dir->read()) { - if ($entry == '.' || $entry == '..') { - continue; - } - - recurseCopy("$source/$entry", "$dest/$entry"); - } - - $dir->close(); - return true; -} +$updateWriteRepository = $kernel->getContainer()->get(WriteUpdateRepositoryInterface::class); $parameters = filter_input_array(INPUT_POST); -$current = filter_var($_POST['current'] ?? "step 5", FILTER_SANITIZE_STRING); +$current = filter_var($_POST['current'] ?? "step 5", FILTER_SANITIZE_FULL_SPECIAL_CHARS); if ($parameters) { if ((int)$parameters["send_statistics"] === 1) { @@ -88,16 +59,19 @@ function recurseCopy($source, $dest) $db->query($query); } -$name = 'install-' . $_SESSION['CURRENT_VERSION'] . '-' . date('Ymd_His'); -$completeName = _CENTREON_VARLIB_ . '/installs/' . $name; -$sourceInstallDir = str_replace('step_upgrade', '', realpath(dirname(__FILE__) . '/../')); - try { - if (recurseCopy($sourceInstallDir, $completeName)) { - recurseRmdir($sourceInstallDir); + if (!isset($_SESSION['CURRENT_VERSION']) || ! preg_match('/^\d+\.\d+\.\d+/', $_SESSION['CURRENT_VERSION'])) { + throw new \Exception('Cannot get current version'); } -} catch (Exception $e) { - exitUpgradeProcess(1, $current, '', $e->getMessage()); + + $updateWriteRepository->runPostUpdate($_SESSION['CURRENT_VERSION']); +} catch (\Throwable $e) { + exitUpgradeProcess( + 1, + $current, + '', + UpdateVersionsException::errorWhenApplyingPostUpdate($e)->getMessage() + ); } session_destroy(); From 0f71be5910ccd565ad87bf3a6c934317a55e16b6 Mon Sep 17 00:00:00 2001 From: hyahiaoui-ext <97593234+hyahiaoui-ext@users.noreply.github.com> Date: Fri, 29 Jul 2022 15:49:54 +0100 Subject: [PATCH 14/18] Clean(platform): Clean appKey method and usage 22.04.x (#11452) * Clean(platform): Clean appKey method and usage (#11336) * removing appKey from information table in baseConf and 22.10 update script * removing appKey from NotifyMasterService.php * removing appKey from CentreonRemoteServer.php * applying suggested changes * Applying suggested changes Co-authored-by: Kevin Duret * adding 22.04.2 update script file with changes * revert 22.04 beta 1 script to its original Co-authored-by: Kevin Duret --- .../Webservice/CentreonRemoteServer.php | 13 +---- .../Domain/Service/NotifyMasterService.php | 9 ---- www/install/createTables.sql | 1 - www/install/php/Update-22.04.2.php | 51 +++++++++++++++++++ www/install/steps/process/insertBaseConf.php | 2 - 5 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 www/install/php/Update-22.04.2.php diff --git a/src/CentreonRemote/Application/Webservice/CentreonRemoteServer.php b/src/CentreonRemote/Application/Webservice/CentreonRemoteServer.php index 40768c67cfe..8bfd90de78f 100644 --- a/src/CentreonRemote/Application/Webservice/CentreonRemoteServer.php +++ b/src/CentreonRemote/Application/Webservice/CentreonRemoteServer.php @@ -109,14 +109,6 @@ public function postAddToWaitList(): string throw new \RestBadRequestException('Can not access your address.'); } - if ( - !isset($_POST['app_key']) - || !$_POST['app_key'] - || empty($appKey = filter_var($_POST['app_key'], FILTER_SANITIZE_STRING)) - ) { - throw new \RestBadRequestException('Please send \'app_key\' in the request.'); - } - if ( !isset($_POST['version']) || !$_POST['version'] @@ -147,15 +139,14 @@ public function postAddToWaitList(): string } $createdAt = date('Y-m-d H:i:s'); - $insertQuery = "INSERT INTO `remote_servers` (`ip`, `app_key`, `version`, `is_connected`, + $insertQuery = "INSERT INTO `remote_servers` (`ip`, `version`, `is_connected`, `created_at`, `http_method`, `http_port`, `no_check_certificate`) - VALUES (:ip, :app_key, :version, 0, '{$createdAt}', + VALUES (:ip, :version, 0, '{$createdAt}', :http_method, :http_port, :no_check_certificate )"; $insert = $this->pearDB->prepare($insertQuery); $insert->bindValue(':ip', $ip, \PDO::PARAM_STR); - $insert->bindValue(':app_key', $appKey, \PDO::PARAM_STR); $insert->bindValue(':version', $version, \PDO::PARAM_STR); $insert->bindValue(':http_method', $httpScheme, \PDO::PARAM_STR); $insert->bindValue(':http_port', $httpPort, \PDO::PARAM_INT); diff --git a/src/CentreonRemote/Domain/Service/NotifyMasterService.php b/src/CentreonRemote/Domain/Service/NotifyMasterService.php index d5b0295f933..9af5d665ddb 100644 --- a/src/CentreonRemote/Domain/Service/NotifyMasterService.php +++ b/src/CentreonRemote/Domain/Service/NotifyMasterService.php @@ -93,19 +93,10 @@ public function pingMaster($ip, $data, $noCheckCertificate = false, $noProxy = f $url = "{$ip}/centreon/api/external.php?object=centreon_remote_server&action=addToWaitList"; $repository = $this->dbManager->getRepository(InformationsRepository::class); - $applicationKey = $repository->getOneByKey('appKey'); $version = $repository->getOneByKey('version'); - if (empty($applicationKey)) { - return [ - 'status' => self::FAIL, - 'details' => self::NO_APP_KEY - ]; - } - try { $curlData = [ - 'app_key' => $applicationKey->getValue(), 'version' => $version->getValue(), 'http_method' => $data['remoteHttpMethod'] ?? 'http', 'http_port' => $data['remoteHttpPort'] ?? '', diff --git a/www/install/createTables.sql b/www/install/createTables.sql index aa1a86c661d..c72f2449beb 100644 --- a/www/install/createTables.sql +++ b/www/install/createTables.sql @@ -2321,7 +2321,6 @@ CREATE TABLE IF NOT EXISTS contact_feature ( CREATE TABLE IF NOT EXISTS `remote_servers` ( `id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `ip` VARCHAR(255) NOT NULL, - `app_key` VARCHAR(40) NOT NULL, `version` VARCHAR(16) NOT NULL, `is_connected` TINYINT(1) NOT NULL DEFAULT 0, `created_at` TIMESTAMP NOT NULL, diff --git a/www/install/php/Update-22.04.2.php b/www/install/php/Update-22.04.2.php new file mode 100644 index 00000000000..18f7e537d45 --- /dev/null +++ b/www/install/php/Update-22.04.2.php @@ -0,0 +1,51 @@ +beginTransaction(); + + $errorMessage = "Unable to delete 'appKey' information from database"; + $pearDB->query("DELETE FROM `informations` WHERE `key` = 'appKey'"); + + $pearDB->commit(); +} catch (\Exception $e) { + if ($pearDB->inTransaction()) { + $pearDB->rollBack(); + } + + $centreonLog->insertLog( + 4, + $versionOfTheUpgrade . $errorMessage . + " - Code : " . (int)$e->getCode() . + " - Error : " . $e->getMessage() . + " - Trace : " . $e->getTraceAsString() + ); + + throw new \Exception($versionOfTheUpgrade . $errorMessage, (int) $e->getCode(), $e); +} diff --git a/www/install/steps/process/insertBaseConf.php b/www/install/steps/process/insertBaseConf.php index 5a2fe96e73a..95f3e2bab70 100644 --- a/www/install/steps/process/insertBaseConf.php +++ b/www/install/steps/process/insertBaseConf.php @@ -138,9 +138,7 @@ $link->exec("INSERT INTO `options` (`key`, `value`) VALUES ('gmt','" . $timezoneId . "')"); # Generate random key for this instance and set it to be not central and not remote -$uniqueKey = md5(uniqid(rand(), true)); $informationsTableInsert = "INSERT INTO `informations` (`key`,`value`) VALUES - ('appKey', '{$uniqueKey}'), ('isRemote', 'no'), ('isCentral', 'yes')"; From 726d4d28badc088cc8283c290c8b3730332736bb Mon Sep 17 00:00:00 2001 From: hyahiaoui-ext <97593234+hyahiaoui-ext@users.noreply.github.com> Date: Fri, 29 Jul 2022 16:35:36 +0100 Subject: [PATCH 15/18] enh(platform): Use API to select metrics in virtual metrics configuration form 22.04.x (#11461) * changing select with select2 of metrics * fix alignement * remove unecessary files and replace selec by select2 in formComponentTemplate * fix select id name for acceptance tests * update composer for acceptance tests * fix acceptance test 2 * add allow clear to metrics select2 * applying suggested changes * final changes for merging * remove unecessary select tag --- composer.lock | 12 +- .../bootstrap/VirtualMetricHandleContext.php | 3 +- .../formComponentTemplate.ihtml | 3 + .../formComponentTemplate.php | 22 ++- .../graphs/common/makeJS_formMetricsList.php | 177 ------------------ .../graphs/common/makeXML_ListMetrics.php | 173 ----------------- .../virtualMetrics/formVirtualMetrics.ihtml | 5 +- .../virtualMetrics/formVirtualMetrics.php | 24 ++- 8 files changed, 48 insertions(+), 371 deletions(-) delete mode 100644 www/include/views/graphs/common/makeJS_formMetricsList.php delete mode 100644 www/include/views/graphs/common/makeXML_ListMetrics.php diff --git a/composer.lock b/composer.lock index 6dc53f71f39..bf11612a345 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1c4ccf2b3d1f768dfd30298637120a27", + "content-hash": "668e34fd2ddb66b073d8e525d65c166a", "packages": [ { "name": "beberlei/assert", @@ -6628,12 +6628,12 @@ "source": { "type": "git", "url": "https://github.com/centreon/centreon-test-lib.git", - "reference": "2aed30ebf46d7b76478166fdf122112a1c3722c6" + "reference": "1f51490fe248f71e8c464c4ebfb5d0f04af7a129" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/centreon/centreon-test-lib/zipball/2aed30ebf46d7b76478166fdf122112a1c3722c6", - "reference": "2aed30ebf46d7b76478166fdf122112a1c3722c6", + "url": "https://api.github.com/repos/centreon/centreon-test-lib/zipball/1f51490fe248f71e8c464c4ebfb5d0f04af7a129", + "reference": "1f51490fe248f71e8c464c4ebfb5d0f04af7a129", "shasum": "" }, "require": { @@ -6678,7 +6678,7 @@ "issues": "https://github.com/centreon/centreon-test-lib/issues", "source": "https://github.com/centreon/centreon-test-lib/tree/master" }, - "time": "2022-04-27T09:10:57+00:00" + "time": "2022-07-29T09:34:33+00:00" }, { "name": "facade/ignition-contracts", @@ -10964,5 +10964,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/features/bootstrap/VirtualMetricHandleContext.php b/features/bootstrap/VirtualMetricHandleContext.php index 47c44733822..16130030d81 100644 --- a/features/bootstrap/VirtualMetricHandleContext.php +++ b/features/bootstrap/VirtualMetricHandleContext.php @@ -24,7 +24,8 @@ public function iAddAVirtualMetric() $this->page = new MetricsConfigurationPage($this); $this->page->setProperties(array( 'name' => $this->vmName, - 'linked-host_services' => $this->host . ' - ' . $this->hostService + 'linked-host_services' => $this->host . ' - ' . $this->hostService, + 'known_metrics' => $this->functionRPN, )); $this->page->setProperties(array('function' => $this->functionRPN)); $this->page->save(); diff --git a/www/include/views/componentTemplates/formComponentTemplate.ihtml b/www/include/views/componentTemplates/formComponentTemplate.ihtml index 8afbb317413..ede927f9029 100644 --- a/www/include/views/componentTemplates/formComponentTemplate.ihtml +++ b/www/include/views/componentTemplates/formComponentTemplate.ihtml @@ -35,6 +35,9 @@    + + + {/if} diff --git a/www/include/views/componentTemplates/formComponentTemplate.php b/www/include/views/componentTemplates/formComponentTemplate.php index faaec375c5b..4b27228c4e3 100644 --- a/www/include/views/componentTemplates/formComponentTemplate.php +++ b/www/include/views/componentTemplates/formComponentTemplate.php @@ -359,7 +359,7 @@ function insertValueQuery() { var e_input = document.Form.ds_name; var e_select = document.getElementById('sl_list_metrics'); var sd_o = e_select.selectedIndex; - if (sd_o != 0) { + if (sd_o != -1) { var chaineAj = ''; chaineAj = e_select.options[sd_o].text; chaineAj = chaineAj.replace(/\s(\[[CV]DEF\]|)\s*$/, ""); @@ -431,7 +431,6 @@ function popup_color_picker(t,name) } $vdef = 0; /* don't list VDEF in metrics list */ -include_once('./include/views/graphs/common/makeJS_formMetricsList.php'); if ($o === MODIFY_COMPONENT_TEMPLATE || $o === WATCH_COMPONENT_TEMPLATE) { $host_service_id = filter_var( $_POST['host_service_id'] ?? ($compo["host_id"] . '-' . $compo['service_id']), @@ -446,9 +445,20 @@ function popup_color_picker(t,name) ?> diff --git a/www/include/views/graphs/common/makeJS_formMetricsList.php b/www/include/views/graphs/common/makeJS_formMetricsList.php deleted file mode 100644 index b817ce94a11..00000000000 --- a/www/include/views/graphs/common/makeJS_formMetricsList.php +++ /dev/null @@ -1,177 +0,0 @@ -. - * - * Linking this program statically or dynamically with other modules is making a - * combined work based on this program. Thus, the terms and conditions of the GNU - * General Public License cover the whole combination. - * - * As a special exception, the copyright holders of this program give Centreon - * permission to link this program with independent modules to produce an executable, - * regardless of the license terms of these independent modules, and to copy and - * distribute the resulting executable under terms of Centreon choice, provided that - * Centreon also meet, for each linked independent module, the terms and conditions - * of the license of that module. An independent module is a module which is not - * derived from this program. If you modify this program, you may extend this - * exception to your version of the program, but you are not obliged to do so. If you - * do not wish to do so, delete this exception statement from your version. - * - * For more information : contact@centreon.com - * - * SVN : $URL$ - * SVN : $Id$ - * - */ - - /* - * Lang file - */ - $locale = $oreon->user->get_lang(); - putenv("LANG=$locale"); - setlocale(LC_ALL, $locale); - bindtextdomain("messages", _CENTREON_PATH_ . "www/locale/"); - bind_textdomain_codeset("messages", "UTF-8"); - textdomain("messages"); -?> diff --git a/www/include/views/graphs/common/makeXML_ListMetrics.php b/www/include/views/graphs/common/makeXML_ListMetrics.php deleted file mode 100644 index 5d7afe858b1..00000000000 --- a/www/include/views/graphs/common/makeXML_ListMetrics.php +++ /dev/null @@ -1,173 +0,0 @@ -. - * - * Linking this program statically or dynamically with other modules is making a - * combined work based on this program. Thus, the terms and conditions of the GNU - * General Public License cover the whole combination. - * - * As a special exception, the copyright holders of this program give Centreon - * permission to link this program with independent modules to produce an executable, - * regardless of the license terms of these independent modules, and to copy and - * distribute the resulting executable under terms of Centreon choice, provided that - * Centreon also meet, for each linked independent module, the terms and conditions - * of the license of that module. An independent module is a module which is not - * derived from this program. If you modify this program, you may extend this - * exception to your version of the program, but you are not obliged to do so. If you - * do not wish to do so, delete this exception statement from your version. - * - * For more information : contact@centreon.com - * - * SVN : $URL$ - * SVN : $Id$ - * - */ - - header('Content-Type: text/xml'); - header('Cache-Control: no-cache'); - - require_once realpath(dirname(__FILE__) . "/../../../../../config/centreon.config.php"); - require_once _CENTREON_PATH_."/www/class/centreonDB.class.php"; - require_once _CENTREON_PATH_."/www/class/centreonXML.class.php"; - -function compare($a, $b) -{ - if ($a["metric_name"] == $b["metric_name"]) { - return 0; - } - return ( $a["metric_name"] < $b["metric_name"] ) ? -1 : 1; -} - - $pearDB = new CentreonDB(); - $pearDBO = new CentreonDB("centstorage"); - - /* - * Get session - */ - require_once(_CENTREON_PATH_ . "www/class/centreonSession.class.php"); - require_once(_CENTREON_PATH_ . "www/class/centreon.class.php"); -if (!isset($_SESSION['centreon'])) { - CentreonSession::start(); -} - -if (isset($_SESSION['centreon'])) { - $oreon = $_SESSION['centreon']; -} else { - exit; -} - - /* - * Get language - */ - $locale = $oreon->user->get_lang(); - putenv("LANG=$locale"); - setlocale(LC_ALL, $locale); - bindtextdomain("messages", _CENTREON_PATH_ . "www/locale/"); -; - bind_textdomain_codeset("messages", "UTF-8"); - textdomain("messages"); - - # - # Existing Real Metric List comes from DBO -> Store in $rmetrics Array - # - $s_datas = array(); - $o_datas = array(""=> utf8_decode(_("List of known metrics"))); - $mx_l = strlen($o_datas[""]); - $where = ""; - $def_type = array(0=>"CDEF",1=>"VDEF"); - -if (isset($_GET['vdef']) && is_numeric($_GET['vdef']) && $_GET['vdef'] == 0) { - $where = " AND def_type='".$_GET["vdef"]."'"; -} - -if (isset($_GET["host_id"]) && $_GET["service_id"]) { - if (!is_numeric($_GET['host_id']) || !is_numeric($_GET['service_id'])) { - $buffer = new CentreonXML(); - $buffer->writeElement('error', 'Bad id format'); - $buffer->output(); - exit; - } - $host_id = $_GET["host_id"]; - $service_id = $_GET["service_id"]; - - $query = "SELECT id " - . "FROM index_data " - . "WHERE host_id = " . $pearDB->escape($host_id) . " " - . "AND service_id = " . $pearDB->escape($service_id) . " "; - - $index_id = 0; - $pq_sql = $pearDBO->query($query); - if ($row = $pq_sql->fetchRow()) { - $index_id = $row['id']; - } - - $query = "SELECT metric_id, metric_name " - . "FROM metrics " - . "WHERE index_id = " . $index_id . " "; - $pq_sql = $pearDBO->query($query); - while ($fw_sql = $pq_sql->fetchRow()) { - $sd_l = strlen($fw_sql["metric_name"]); - $fw_sql["metric_name"] = $fw_sql["metric_name"] . "   "; - $s_datas[] = $fw_sql; - if ($sd_l > $mx_l) { - $mx_l = $sd_l; - } - } - $pq_sql->closeCursor(); - $query = "SELECT vmetric_id, vmetric_name, def_type " - . "FROM virtual_metrics " - . "WHERE index_id = " . $index_id . " " - . $where . " "; - $pq_sql = $pearDB->query($query); - - while ($fw_sql = $pq_sql->fetchRow()) { - $sd_l = strlen($fw_sql["vmetric_name"]." [CDEF]"); - $fw_sql["metric_name"] = $fw_sql["vmetric_name"]." [".$def_type[$fw_sql["def_type"]]."]   "; - $fw_sql["metric_id"] = "v".$fw_sql["vmetric_id"]; - $s_datas[] = $fw_sql; - if ($sd_l > $mx_l) { - $mx_l = $sd_l; - } - $pq_sql->closeCursor(); - } -} - - usort($s_datas, "compare"); - -foreach ($s_datas as $key => $om) { - $o_datas[$om["metric_id"]] = $om["metric_name"]; -} - -for ($i = strlen($o_datas[""]); $i != $mx_l; $i++) { - $o_datas[""] .= " "; -} - - # The first element of the select is empty - $buffer = new CentreonXML(); - $buffer->startElement("options_data"); - $buffer->writeElement("td_id", "td_list_metrics"); - $buffer->writeElement("select_id", "sl_list_metrics"); - - # Now we fill out the select with templates id and names -foreach ($o_datas as $o_id => $o_alias) { - $buffer->startElement("option"); - $buffer->writeElement("o_id", $o_id); - $buffer->writeElement("o_alias", $o_alias); - $buffer->endElement(); -} - - $buffer->endElement(); - $buffer->output(); diff --git a/www/include/views/virtualMetrics/formVirtualMetrics.ihtml b/www/include/views/virtualMetrics/formVirtualMetrics.ihtml index 86c044fee1a..98ea810739a 100644 --- a/www/include/views/virtualMetrics/formVirtualMetrics.ihtml +++ b/www/include/views/virtualMetrics/formVirtualMetrics.ihtml @@ -48,7 +48,10 @@ {$form.rpn_function.html} {if $o == "a" || $o == "c"} -    +    + + + {/if} diff --git a/www/include/views/virtualMetrics/formVirtualMetrics.php b/www/include/views/virtualMetrics/formVirtualMetrics.php index a90eb4cf6d6..4cf98e972b9 100644 --- a/www/include/views/virtualMetrics/formVirtualMetrics.php +++ b/www/include/views/virtualMetrics/formVirtualMetrics.php @@ -236,7 +236,7 @@ function insertValueQuery() { var e_txtarea = document.Form.rpn_function; var e_select = document.getElementById('sl_list_metrics'); var sd_o = e_select.selectedIndex; - if (sd_o != 0) { + if (sd_o != -1) { var chaineAj = ''; chaineAj = e_select.options[sd_o].text; //chaineAj = chaineAj.substring(0, chaineAj.length - 3); @@ -329,7 +329,7 @@ function manageVDEF() { $tpl->display("formVirtualMetrics.ihtml"); } $vdef = 1; /* Display VDEF too */ -include_once("./include/views/graphs/common/makeJS_formMetricsList.php"); + if ($o == METRIC_MODIFY || $o == METRIC_WATCH) { isset($_POST["host_id"]) && $_POST["host_id"] != null ? $host_service_id = $_POST["host_id"] @@ -340,11 +340,21 @@ function manageVDEF() { : $host_service_id = 0; } ?> - From 5dda7fc75f10d4790e8aa4035499a4272e55ac71 Mon Sep 17 00:00:00 2001 From: Elmahdi ABBASSI <108519266+emabassi-ext@users.noreply.github.com> Date: Sun, 31 Jul 2022 18:49:38 +0100 Subject: [PATCH 16/18] [SNYK] Sanitize and bind ACL class queries (#11392) (#11472) * Sanitize and bind ACL class queries Queries sanitized and bound using PDO statement * fix spaces spaces between (int) cast and variables * update file delete spaces after comma * change variables names due to a review * Line exceeds 120 characters; contains 123 characters --- www/class/centreonACL.class.php | 50 ++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/www/class/centreonACL.class.php b/www/class/centreonACL.class.php index e1e244c7ca3..699419dd29a 100644 --- a/www/class/centreonACL.class.php +++ b/www/class/centreonACL.class.php @@ -400,15 +400,17 @@ private function setTopology() if ($DBRESULT->rowCount()) { $topology = array(); $tmp_topo_page = array(); - while ($topo_group = $DBRESULT->fetchRow()) { - $query2 = "SELECT topology_topology_id, acl_topology_relations.access_right " + $statement = $centreonDb + ->prepare("SELECT topology_topology_id, acl_topology_relations.access_right " . "FROM acl_topology_relations, acl_topology " . "WHERE acl_topology.acl_topo_activate = '1' " . "AND acl_topology.acl_topo_id = acl_topology_relations.acl_topo_id " - . "AND acl_topology_relations.acl_topo_id = '" . $topo_group["acl_topology_id"] . "' " - . "AND acl_topology_relations.access_right != 0"; // do not get "access none" - $DBRESULT2 = $centreonDb->query($query2); - while ($topo_page = $DBRESULT2->fetchRow()) { + . "AND acl_topology_relations.acl_topo_id = :acl_topology_id " + . "AND acl_topology_relations.access_right != 0"); + while ($topo_group = $DBRESULT->fetchRow()) { + $statement->bindValue(':acl_topology_id', (int) $topo_group["acl_topology_id"], \PDO::PARAM_INT); + $statement->execute(); + while ($topo_page = $statement->fetchRow()) { $topology[] = (int) $topo_page["topology_topology_id"]; if (!isset($tmp_topo_page[$topo_page['topology_topology_id']])) { $tmp_topo_page[$topo_page["topology_topology_id"]] = $topo_page["access_right"]; @@ -423,7 +425,7 @@ private function setTopology() } } } - $DBRESULT2->closeCursor(); + $statement->closeCursor(); } $DBRESULT->closeCursor(); @@ -1691,22 +1693,28 @@ public function updateACL($data = null) $request = "SELECT group_id FROM centreon_acl " . "WHERE host_id = " . $data['duplicate_host'] . " AND service_id IS NULL"; $DBRESULT = \CentreonDBInstance::getMonInstance()->query($request); + $hostAclStatement = \CentreonDBInstance::getMonInstance() + ->prepare("INSERT INTO centreon_acl (host_id, service_id, group_id) " + . "VALUES (:data_id, NULL, :group_id)"); + $serviceAclStatement = \CentreonDBInstance::getMonInstance() + ->prepare("INSERT INTO centreon_acl (host_id, service_id, group_id) " + . "VALUES (:data_id, :service_id, :group_id) " + . "ON DUPLICATE KEY UPDATE group_id = :group_id"); while ($row = $DBRESULT->fetchRow()) { // Insert New Host - $request1 = "INSERT INTO centreon_acl (host_id, service_id, group_id) " - . "VALUES ('" . $data["id"] . "', NULL, " . $row['group_id'] . ")"; - \CentreonDBInstance::getMonInstance()->query($request1); - + $hostAclStatement->bindValue(':data_id', (int) $data["id"], \PDO::PARAM_INT); + $hostAclStatement->bindValue(':group_id', (int) $row['group_id'], \PDO::PARAM_INT); + $hostAclStatement->execute(); // Insert services $request = "SELECT service_id, group_id FROM centreon_acl " . "WHERE host_id = " . $data['duplicate_host'] . " AND service_id IS NOT NULL"; $DBRESULT2 = \CentreonDBInstance::getMonInstance()->query($request); while ($row2 = $DBRESULT2->fetch()) { - $request2 = "INSERT INTO centreon_acl (host_id, service_id, group_id) " - . "VALUES ('" . $data["id"] . "', " - . "'" . $row2["service_id"] . "', " . $row2['group_id'] . ") " - . "ON DUPLICATE KEY UPDATE group_id = " . $row2['group_id']; - \CentreonDBInstance::getMonInstance()->query($request2); + $serviceAclStatement->bindValue(':data_id', (int) $data["id"], \PDO::PARAM_INT); + $serviceAclStatement + ->bindValue(':service_id', (int) $row2["service_id"], \PDO::PARAM_INT); + $serviceAclStatement->bindValue(':group_id', (int) $row2['group_id'], \PDO::PARAM_INT); + $serviceAclStatement->execute(); } } } @@ -1730,10 +1738,14 @@ public function updateACL($data = null) $request = "SELECT group_id FROM centreon_acl " . "WHERE host_id = $host_id AND service_id = " . $data['duplicate_service']; $DBRESULT = \CentreonDBInstance::getMonInstance()->query($request); + $statement = \CentreonDBInstance::getMonInstance() + ->prepare("INSERT INTO centreon_acl (host_id, service_id, group_id) " + . "VALUES (:host_id, :data_id, :group_id)"); while ($row = $DBRESULT->fetchRow()) { - $request2 = "INSERT INTO centreon_acl (host_id, service_id, group_id) " - . "VALUES ('" . $host_id . "', '" . $data["id"] . "', " . $row['group_id'] . ")"; - \CentreonDBInstance::getMonInstance()->query($request2); + $statement->bindValue(':host_id', (int) $host_id, \PDO::PARAM_INT); + $statement->bindValue(':data_id', (int) $data["id"], \PDO::PARAM_INT); + $statement->bindValue(':group_id', (int) $row['group_id'], \PDO::PARAM_INT); + $statement->execute(); } } } From 4dabe5300e48fce47c6b95bb737cd76ae91cb6dc Mon Sep 17 00:00:00 2001 From: alaunois Date: Mon, 1 Aug 2022 09:54:41 +0200 Subject: [PATCH 17/18] fix(conf) fix broker conf name display in listing (#11372) (#11376) --- .../configuration/configCentreonBroker/listCentreonBroker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/include/configuration/configCentreonBroker/listCentreonBroker.php b/www/include/configuration/configCentreonBroker/listCentreonBroker.php index 7e2e86df5ad..c8f025e3cce 100644 --- a/www/include/configuration/configCentreonBroker/listCentreonBroker.php +++ b/www/include/configuration/configCentreonBroker/listCentreonBroker.php @@ -169,7 +169,7 @@ $elemArr[$i] = array( "MenuClass" => "list_" . $style, "RowMenu_select" => $selectedElements->toHtml(), - "RowMenu_name" => CentreonUtils::escapeSecure($config["config_name"]), + "RowMenu_name" => htmlentities($config["config_name"], ENT_QUOTES, 'UTF-8'), "RowMenu_link" => "main.php?p=" . $p . "&o=c&id=" . $config['config_id'], "RowMenu_desc" => CentreonUtils::escapeSecure( substr( From f5a745a9001984e8f1d76e27c8c12b1908a88f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Duret?= Date: Tue, 2 Aug 2022 11:57:22 +0200 Subject: [PATCH 18/18] fix(web): fix the comment deletion for host monitored by poller (#11138) Refs: MON-12828 --- www/include/monitoring/comments/comments.php | 2 +- www/include/monitoring/comments/common-Func.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/www/include/monitoring/comments/comments.php b/www/include/monitoring/comments/comments.php index d9761619ea6..11bfee39976 100644 --- a/www/include/monitoring/comments/comments.php +++ b/www/include/monitoring/comments/comments.php @@ -78,7 +78,7 @@ if (!empty($select)) { foreach ($select as $key => $value) { $res = explode(';', urldecode($key)); - DeleteComment($res[0], [(int)$res[1] . ';' . (int)$res[2] => 'on']); + DeleteComment($res[0], [$res[1] . ';' . (int)$res[2] => 'on']); } } } else { diff --git a/www/include/monitoring/comments/common-Func.php b/www/include/monitoring/comments/common-Func.php index 0cface9ab7b..2439ce74064 100644 --- a/www/include/monitoring/comments/common-Func.php +++ b/www/include/monitoring/comments/common-Func.php @@ -47,7 +47,6 @@ function DeleteComment($type = null, $hosts = []) foreach ($hosts as $key => $value) { $res = preg_split("/\;/", $key); - $res[0] = filter_var($res[0] ?? 0, FILTER_VALIDATE_INT); $res[1] = filter_var($res[1] ?? 0, FILTER_VALIDATE_INT); write_command(" DEL_" . $type . "_COMMENT;" . $res[1], GetMyHostPoller($pearDB, $res[0])); }