[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 <alexa1866@gmail.com> Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com> Co-authored-by: Dan Cech <dcech@grafana.com>
This commit is contained in:
co-authored by
Alexandra Vargas
Ashley Harrison
Dan Cech
parent
097235d7c4
commit
7be31686ca
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
export function sanitizeAngularInterpolation(url: string): string {
|
||||
return url.replace(/\{\{/g, '%7B%7B').replace(/\}\}/g, '%7D%7D');
|
||||
}
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
@@ -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<Props, State> {
|
||||
children = getFooterLinks();
|
||||
}
|
||||
|
||||
const sanitizedUrl = textUtil.sanitizeAngularInterpolation(link.url ?? '');
|
||||
return (
|
||||
<div className="sidemenu-item dropdown dropup">
|
||||
<Link href={link.url} className="sidemenu-link" target={link.target}>
|
||||
<Link href={sanitizedUrl} className="sidemenu-link" target={link.target}>
|
||||
<span className="icon-circle sidemenu-icon">
|
||||
{link.icon && <Icon name={link.icon as IconName} size="xl" title="Help icon" />}
|
||||
{link.img && <img src={link.img} alt="Profile picture" />}
|
||||
@@ -78,9 +79,10 @@ export default class BottomNavLinks extends PureComponent<Props, State> {
|
||||
{showSwitcherModal && <OrgSwitcher onDismiss={this.toggleSwitcherModal} />}
|
||||
|
||||
{children.map((child, index) => {
|
||||
const sanitizedChildrenUrl = textUtil.sanitizeAngularInterpolation(child.url ?? '');
|
||||
return (
|
||||
<li key={`${child.text}-${index}`}>
|
||||
<a href={child.url} target={child.target} rel="noopener">
|
||||
<a href={sanitizedChildrenUrl} target={child.target} rel="noopener">
|
||||
{child.icon && <Icon name={child.icon as IconName} className={subMenuIconClassName} />}
|
||||
{child.text}
|
||||
</a>
|
||||
|
||||
@@ -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> = (props) => {
|
||||
</>
|
||||
);
|
||||
|
||||
const anchor = child.url ? <Link href={child.url}>{linkContent}</Link> : <a>{linkContent}</a>;
|
||||
const sanitizedUrl = textUtil.sanitizeAngularInterpolation(child.url ?? '');
|
||||
const anchor = child.url ? <Link href={sanitizedUrl}>{linkContent}</Link> : <a>{linkContent}</a>;
|
||||
|
||||
return <li className={listItemClassName}>{anchor}</li>;
|
||||
};
|
||||
|
||||
@@ -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> = (props) => {
|
||||
childrenLinks = filter(link.children, (item) => !item.hideFromMenu);
|
||||
}
|
||||
|
||||
const sanitizedUrl = textUtil.sanitizeAngularInterpolation(link.url ?? '');
|
||||
|
||||
const linkContent = <span className="sidemenu-item-text">{link.text}</span>;
|
||||
const anchor = link.url ? (
|
||||
<Link href={link.url} onClick={onHeaderClick} className="side-menu-header-link">
|
||||
<Link href={sanitizedUrl} onClick={onHeaderClick} className="side-menu-header-link">
|
||||
{linkContent}
|
||||
</Link>
|
||||
) : (
|
||||
|
||||
@@ -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<any> = () => {
|
||||
const location = useLocation();
|
||||
const forcedLoginUrl = getForcedLoginUrl(location.pathname + location.search);
|
||||
const sanitizedUrl = textUtil.sanitizeAngularInterpolation(forcedLoginUrl);
|
||||
|
||||
return (
|
||||
<div className="sidemenu-item">
|
||||
<a href={forcedLoginUrl} className="sidemenu-link" target="_self">
|
||||
<a href={sanitizedUrl} className="sidemenu-link" target="_self">
|
||||
<span className="icon-circle sidemenu-icon">
|
||||
<Icon name="signout" size="xl" />
|
||||
</span>
|
||||
</a>
|
||||
<a href={forcedLoginUrl} target="_self">
|
||||
<a href={sanitizedUrl} target="_self">
|
||||
<ul className="dropdown-menu dropdown-menu--sidemenu" role="menu">
|
||||
<li className="side-menu-header">
|
||||
<span className="sidemenu-item-text">Sign In</span>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { FC } from 'react';
|
||||
import SideMenuDropDown from './SideMenuDropDown';
|
||||
import { Icon, Link, useStyles2 } from '@grafana/ui';
|
||||
import { NavModelItem } from '@grafana/data';
|
||||
import { NavModelItem, textUtil } from '@grafana/data';
|
||||
import { css, cx } from '@emotion/css';
|
||||
|
||||
export interface Props {
|
||||
@@ -23,11 +23,12 @@ const TopSectionItem: FC<Props> = ({ link, onClick }) => {
|
||||
{link.img && <img src={link.img} />}
|
||||
</span>
|
||||
);
|
||||
const sanitizedUrl = textUtil.sanitizeAngularInterpolation(link.url ?? '');
|
||||
|
||||
const anchor = link.url ? (
|
||||
<Link
|
||||
className="sidemenu-link"
|
||||
href={link.url}
|
||||
href={sanitizedUrl}
|
||||
target={link.target}
|
||||
aria-label={link.text}
|
||||
onClick={onClick}
|
||||
|
||||
@@ -6,6 +6,7 @@ exports[`Render should render children 1`] = `
|
||||
>
|
||||
<Link
|
||||
className="sidemenu-link"
|
||||
href=""
|
||||
>
|
||||
<span
|
||||
className="icon-circle sidemenu-icon"
|
||||
@@ -19,6 +20,7 @@ exports[`Render should render children 1`] = `
|
||||
key="undefined-0"
|
||||
>
|
||||
<a
|
||||
href=""
|
||||
rel="noopener"
|
||||
/>
|
||||
</li>
|
||||
@@ -26,6 +28,7 @@ exports[`Render should render children 1`] = `
|
||||
key="undefined-1"
|
||||
>
|
||||
<a
|
||||
href=""
|
||||
rel="noopener"
|
||||
/>
|
||||
</li>
|
||||
@@ -33,6 +36,7 @@ exports[`Render should render children 1`] = `
|
||||
key="undefined-2"
|
||||
>
|
||||
<a
|
||||
href=""
|
||||
rel="noopener"
|
||||
/>
|
||||
</li>
|
||||
@@ -40,6 +44,7 @@ exports[`Render should render children 1`] = `
|
||||
key="undefined-3"
|
||||
>
|
||||
<a
|
||||
href=""
|
||||
rel="noopener"
|
||||
/>
|
||||
</li>
|
||||
@@ -60,6 +65,7 @@ exports[`Render should render component 1`] = `
|
||||
>
|
||||
<Link
|
||||
className="sidemenu-link"
|
||||
href=""
|
||||
>
|
||||
<span
|
||||
className="icon-circle sidemenu-icon"
|
||||
@@ -88,6 +94,7 @@ exports[`Render should render organization switcher 1`] = `
|
||||
>
|
||||
<Link
|
||||
className="sidemenu-link"
|
||||
href=""
|
||||
>
|
||||
<span
|
||||
className="icon-circle sidemenu-icon"
|
||||
@@ -146,6 +153,7 @@ exports[`Render should render subtitle 1`] = `
|
||||
>
|
||||
<Link
|
||||
className="sidemenu-link"
|
||||
href=""
|
||||
>
|
||||
<span
|
||||
className="icon-circle sidemenu-icon"
|
||||
|
||||
Reference in New Issue
Block a user