From 7be31686ca976754412cd6853ea47739fffa0a9d Mon Sep 17 00:00:00 2001 From: Dimitris Sotirakis Date: Tue, 9 Nov 2021 10:30:07 +0200 Subject: [PATCH] [v8.1.x] Navbar: Add sanitize validation to urls (#41311) * Navbar: Add sanitize validation to urls * NavBar: Change help link from '#' to '/' * global replace * Fix tests Co-authored-by: Alexandra Vargas Co-authored-by: Ashley Harrison Co-authored-by: Dan Cech --- packages/grafana-data/src/text/index.ts | 3 ++- packages/grafana-data/src/text/sanitize.ts | 4 ++++ pkg/api/index.go | 2 +- public/app/core/components/sidemenu/BottomNavLinks.tsx | 8 +++++--- public/app/core/components/sidemenu/DropDownChild.tsx | 4 +++- public/app/core/components/sidemenu/SideMenuDropDown.tsx | 6 ++++-- public/app/core/components/sidemenu/SignIn.tsx | 6 ++++-- public/app/core/components/sidemenu/TopSectionItem.tsx | 5 +++-- .../sidemenu/__snapshots__/BottomNavLinks.test.tsx.snap | 8 ++++++++ 9 files changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/grafana-data/src/text/index.ts b/packages/grafana-data/src/text/index.ts index bece68920ac..be85edccaf9 100644 --- a/packages/grafana-data/src/text/index.ts +++ b/packages/grafana-data/src/text/index.ts @@ -1,11 +1,12 @@ export * from './string'; export * from './markdown'; export * from './text'; -import { escapeHtml, hasAnsiCodes, sanitize, sanitizeUrl } from './sanitize'; +import { escapeHtml, hasAnsiCodes, sanitize, sanitizeUrl, sanitizeAngularInterpolation } from './sanitize'; export const textUtil = { escapeHtml, hasAnsiCodes, sanitize, sanitizeUrl, + sanitizeAngularInterpolation, }; diff --git a/packages/grafana-data/src/text/sanitize.ts b/packages/grafana-data/src/text/sanitize.ts index d32c4a96178..ae14afed422 100644 --- a/packages/grafana-data/src/text/sanitize.ts +++ b/packages/grafana-data/src/text/sanitize.ts @@ -38,3 +38,7 @@ export function hasAnsiCodes(input: string): boolean { export function escapeHtml(str: string): string { return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } + +export function sanitizeAngularInterpolation(url: string): string { + return url.replace(/\{\{/g, '%7B%7B').replace(/\}\}/g, '%7D%7D'); +} diff --git a/pkg/api/index.go b/pkg/api/index.go index 51ce5a63325..30769490348 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -344,7 +344,7 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto Text: "Help", SubTitle: helpVersion, Id: "help", - Url: "#", + Url: "/", Icon: "question-circle", HideFromMenu: true, SortWeight: dtos.WeightHelp, diff --git a/public/app/core/components/sidemenu/BottomNavLinks.tsx b/public/app/core/components/sidemenu/BottomNavLinks.tsx index f750328e7bb..94749899a9b 100644 --- a/public/app/core/components/sidemenu/BottomNavLinks.tsx +++ b/public/app/core/components/sidemenu/BottomNavLinks.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { css } from '@emotion/css'; import appEvents from '../../app_events'; import { User } from '../../services/context_srv'; -import { NavModelItem } from '@grafana/data'; +import { NavModelItem, textUtil } from '@grafana/data'; import { Icon, IconName, Link } from '@grafana/ui'; import { OrgSwitcher } from '../OrgSwitcher'; import { getFooterLinks } from '../Footer/Footer'; @@ -46,9 +46,10 @@ export default class BottomNavLinks extends PureComponent { children = getFooterLinks(); } + const sanitizedUrl = textUtil.sanitizeAngularInterpolation(link.url ?? ''); return (
- + {link.icon && } {link.img && Profile picture} @@ -78,9 +79,10 @@ export default class BottomNavLinks extends PureComponent { {showSwitcherModal && } {children.map((child, index) => { + const sanitizedChildrenUrl = textUtil.sanitizeAngularInterpolation(child.url ?? ''); return (
  • - + {child.icon && } {child.text} diff --git a/public/app/core/components/sidemenu/DropDownChild.tsx b/public/app/core/components/sidemenu/DropDownChild.tsx index d6ddecddd66..c9d0b2017c5 100644 --- a/public/app/core/components/sidemenu/DropDownChild.tsx +++ b/public/app/core/components/sidemenu/DropDownChild.tsx @@ -1,6 +1,7 @@ import React, { FC } from 'react'; import { css } from '@emotion/css'; import { Icon, IconName, Link, useTheme } from '@grafana/ui'; +import { textUtil } from '@grafana/data'; export interface Props { child: any; @@ -21,7 +22,8 @@ const DropDownChild: FC = (props) => { ); - const anchor = child.url ? {linkContent} : {linkContent}; + const sanitizedUrl = textUtil.sanitizeAngularInterpolation(child.url ?? ''); + const anchor = child.url ? {linkContent} : {linkContent}; return
  • {anchor}
  • ; }; diff --git a/public/app/core/components/sidemenu/SideMenuDropDown.tsx b/public/app/core/components/sidemenu/SideMenuDropDown.tsx index 58cb902cd28..241520260c4 100644 --- a/public/app/core/components/sidemenu/SideMenuDropDown.tsx +++ b/public/app/core/components/sidemenu/SideMenuDropDown.tsx @@ -1,7 +1,7 @@ import React, { FC } from 'react'; import { filter } from 'lodash'; import DropDownChild from './DropDownChild'; -import { NavModelItem } from '@grafana/data'; +import { NavModelItem, textUtil } from '@grafana/data'; import { Link } from '@grafana/ui'; interface Props { @@ -16,9 +16,11 @@ const SideMenuDropDown: FC = (props) => { childrenLinks = filter(link.children, (item) => !item.hideFromMenu); } + const sanitizedUrl = textUtil.sanitizeAngularInterpolation(link.url ?? ''); + const linkContent = {link.text}; const anchor = link.url ? ( - + {linkContent} ) : ( diff --git a/public/app/core/components/sidemenu/SignIn.tsx b/public/app/core/components/sidemenu/SignIn.tsx index 10a7db9637a..eeb6f8ac82f 100644 --- a/public/app/core/components/sidemenu/SignIn.tsx +++ b/public/app/core/components/sidemenu/SignIn.tsx @@ -2,19 +2,21 @@ import React, { FC } from 'react'; import { Icon } from '@grafana/ui'; import { useLocation } from 'react-router-dom'; import { getForcedLoginUrl } from './utils'; +import { textUtil } from '@grafana/data'; export const SignIn: FC = () => { const location = useLocation(); const forcedLoginUrl = getForcedLoginUrl(location.pathname + location.search); + const sanitizedUrl = textUtil.sanitizeAngularInterpolation(forcedLoginUrl); return (
    - + - +