mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-04-07 10:28:57 +01:00
Add Security Warning Bar for untrusted notebooks (#970)
* Add Security Warning Bar for untrusted notebooks * Update * Another update * Add a snapshot test * UX updates * More updates * Add tests * Update test snapshot * Update string * Update security message
This commit is contained in:
parent
51f3f9a718
commit
f8ac36962b
@ -277,6 +277,10 @@ export class NotebookComponentBootstrapper {
|
|||||||
return selectors.notebook.isDirty(content.model as Immutable.RecordOf<DocumentRecordProps>);
|
return selectors.notebook.isDirty(content.model as Immutable.RecordOf<DocumentRecordProps>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isNotebookUntrusted(): boolean {
|
||||||
|
return NotebookUtil.isNotebookUntrusted(this.getStore().getState(), this.contentRef);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For display purposes, only return non-killed kernels
|
* For display purposes, only return non-killed kernels
|
||||||
*/
|
*/
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { AppState, ContentRef, selectors } from "@nteract/core";
|
import { AppState, ContentRef, selectors } from "@nteract/core";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
import * as NteractUtil from "../NTeractUtil";
|
import * as NteractUtil from "../NTeractUtil";
|
||||||
|
|
||||||
interface VirtualCommandBarComponentProps {
|
interface VirtualCommandBarComponentProps {
|
||||||
kernelSpecName: string;
|
kernelSpecName: string;
|
||||||
kernelStatus: string;
|
kernelStatus: string;
|
||||||
currentCellType: string;
|
currentCellType: string;
|
||||||
|
isNotebookUntrusted: boolean;
|
||||||
onRender: () => void;
|
onRender: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,7 +22,8 @@ class VirtualCommandBarComponent extends React.Component<VirtualCommandBarCompon
|
|||||||
return (
|
return (
|
||||||
this.props.kernelStatus !== nextProps.kernelStatus ||
|
this.props.kernelStatus !== nextProps.kernelStatus ||
|
||||||
this.props.kernelSpecName !== nextProps.kernelSpecName ||
|
this.props.kernelSpecName !== nextProps.kernelSpecName ||
|
||||||
this.props.currentCellType !== nextProps.currentCellType
|
this.props.currentCellType !== nextProps.currentCellType ||
|
||||||
|
this.props.isNotebookUntrusted !== nextProps.isNotebookUntrusted
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +53,7 @@ const makeMapStateToProps = (
|
|||||||
kernelStatus,
|
kernelStatus,
|
||||||
kernelSpecName,
|
kernelSpecName,
|
||||||
currentCellType,
|
currentCellType,
|
||||||
|
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
|
||||||
} as VirtualCommandBarComponentProps;
|
} as VirtualCommandBarComponentProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +73,7 @@ const makeMapStateToProps = (
|
|||||||
kernelStatus,
|
kernelStatus,
|
||||||
kernelSpecName,
|
kernelSpecName,
|
||||||
currentCellType,
|
currentCellType,
|
||||||
|
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
|
||||||
onRender: initialProps.onRender,
|
onRender: initialProps.onRender,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -14,6 +14,7 @@ import * as cdbActions from "../NotebookComponent/actions";
|
|||||||
import loadTransform from "../NotebookComponent/loadTransform";
|
import loadTransform from "../NotebookComponent/loadTransform";
|
||||||
import { CdbAppState, SnapshotFragment, SnapshotRequest } from "../NotebookComponent/types";
|
import { CdbAppState, SnapshotFragment, SnapshotRequest } from "../NotebookComponent/types";
|
||||||
import { NotebookUtil } from "../NotebookUtil";
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
|
import SecurityWarningBar from "../SecurityWarningBar/SecurityWarningBar";
|
||||||
import { AzureTheme } from "./AzureTheme";
|
import { AzureTheme } from "./AzureTheme";
|
||||||
import "./base.css";
|
import "./base.css";
|
||||||
import CellCreator from "./decorators/CellCreator";
|
import CellCreator from "./decorators/CellCreator";
|
||||||
@ -107,6 +108,7 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="NotebookRendererContainer">
|
<div className="NotebookRendererContainer">
|
||||||
|
<SecurityWarningBar contentRef={this.props.contentRef} />
|
||||||
<div className="NotebookRenderer" ref={this.notebookRendererRef}>
|
<div className="NotebookRenderer" ref={this.notebookRendererRef}>
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<KeyboardShortcuts contentRef={this.props.contentRef}>
|
<KeyboardShortcuts contentRef={this.props.contentRef}>
|
||||||
|
@ -19,6 +19,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disabledRunCellButton {
|
||||||
|
.runCellButton .ms-Button-flexContainer .ms-Button-icon {
|
||||||
|
color: @BaseMediumHigh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.greyStopButton {
|
.greyStopButton {
|
||||||
.runCellButton .ms-Button-flexContainer .ms-Button-icon {
|
.runCellButton .ms-Button-flexContainer .ms-Button-icon {
|
||||||
color: @BaseMediumHigh;
|
color: @BaseMediumHigh;
|
||||||
|
@ -5,6 +5,7 @@ import { Dispatch } from "redux";
|
|||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as cdbActions from "../NotebookComponent/actions";
|
import * as cdbActions from "../NotebookComponent/actions";
|
||||||
import { CdbAppState } from "../NotebookComponent/types";
|
import { CdbAppState } from "../NotebookComponent/types";
|
||||||
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
|
|
||||||
export interface PassedPromptProps {
|
export interface PassedPromptProps {
|
||||||
id: string;
|
id: string;
|
||||||
@ -12,6 +13,7 @@ export interface PassedPromptProps {
|
|||||||
status?: string;
|
status?: string;
|
||||||
executionCount?: number;
|
executionCount?: number;
|
||||||
isHovered?: boolean;
|
isHovered?: boolean;
|
||||||
|
isRunDisabled?: boolean;
|
||||||
runCell?: () => void;
|
runCell?: () => void;
|
||||||
stopCell?: () => void;
|
stopCell?: () => void;
|
||||||
}
|
}
|
||||||
@ -20,6 +22,7 @@ interface ComponentProps {
|
|||||||
id: string;
|
id: string;
|
||||||
contentRef: ContentRef;
|
contentRef: ContentRef;
|
||||||
isHovered?: boolean;
|
isHovered?: boolean;
|
||||||
|
isNotebookUntrusted?: boolean;
|
||||||
children: (props: PassedPromptProps) => React.ReactNode;
|
children: (props: PassedPromptProps) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +50,7 @@ export class PromptPure extends React.Component<Props> {
|
|||||||
runCell: this.props.executeCell,
|
runCell: this.props.executeCell,
|
||||||
stopCell: this.props.stopExecution,
|
stopCell: this.props.stopExecution,
|
||||||
isHovered: this.props.isHovered,
|
isHovered: this.props.isHovered,
|
||||||
|
isRunDisabled: this.props.isNotebookUntrusted,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -75,6 +79,7 @@ const makeMapStateToProps = (_state: CdbAppState, ownProps: ComponentProps): ((s
|
|||||||
status,
|
status,
|
||||||
executionCount,
|
executionCount,
|
||||||
isHovered,
|
isHovered,
|
||||||
|
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import { PassedPromptProps } from "./Prompt";
|
||||||
|
import { promptContent } from "./PromptContent";
|
||||||
|
|
||||||
|
describe("PromptContent", () => {
|
||||||
|
it("renders for busy status", () => {
|
||||||
|
const props: PassedPromptProps = {
|
||||||
|
id: "id",
|
||||||
|
contentRef: "contentRef",
|
||||||
|
status: "busy",
|
||||||
|
};
|
||||||
|
const wrapper = shallow(promptContent(props));
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders when hovered", () => {
|
||||||
|
const props: PassedPromptProps = {
|
||||||
|
id: "id",
|
||||||
|
contentRef: "contentRef",
|
||||||
|
isHovered: true,
|
||||||
|
};
|
||||||
|
const wrapper = shallow(promptContent(props));
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,6 @@
|
|||||||
import { IconButton, Spinner, SpinnerSize } from "@fluentui/react";
|
import { IconButton, Spinner, SpinnerSize } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
import { PassedPromptProps } from "./Prompt";
|
import { PassedPromptProps } from "./Prompt";
|
||||||
import "./Prompt.less";
|
import "./Prompt.less";
|
||||||
|
|
||||||
@ -23,15 +24,18 @@ export const promptContent = (props: PassedPromptProps): JSX.Element => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (props.isHovered) {
|
} else if (props.isHovered) {
|
||||||
const playButtonText = "Run cell";
|
const playButtonText = props.isRunDisabled ? NotebookUtil.UntrustedNotebookRunHint : "Run cell";
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<div className={props.isRunDisabled ? "disabledRunCellButton" : ""}>
|
||||||
className="runCellButton"
|
<IconButton
|
||||||
iconProps={{ iconName: "MSNVideosSolid" }}
|
className="runCellButton"
|
||||||
title={playButtonText}
|
iconProps={{ iconName: "MSNVideosSolid" }}
|
||||||
ariaLabel={playButtonText}
|
title={playButtonText}
|
||||||
onClick={props.runCell}
|
ariaLabel={playButtonText}
|
||||||
/>
|
disabled={props.isRunDisabled}
|
||||||
|
onClick={props.runCell}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return <div style={{ paddingTop: 7 }}>{promptText(props)}</div>;
|
return <div style={{ paddingTop: 7 }}>{promptText(props)}</div>;
|
||||||
|
@ -36,6 +36,7 @@ interface StateProps {
|
|||||||
cellIdAbove: CellId;
|
cellIdAbove: CellId;
|
||||||
cellIdBelow: CellId;
|
cellIdBelow: CellId;
|
||||||
hasCodeOutput: boolean;
|
hasCodeOutput: boolean;
|
||||||
|
isNotebookUntrusted: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & StateProps> {
|
class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & StateProps> {
|
||||||
@ -43,12 +44,16 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
|
|||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
let items: IContextualMenuItem[] = [];
|
let items: IContextualMenuItem[] = [];
|
||||||
|
const isNotebookUntrusted = this.props.isNotebookUntrusted;
|
||||||
|
const runTooltip = isNotebookUntrusted ? NotebookUtil.UntrustedNotebookRunHint : undefined;
|
||||||
|
|
||||||
if (this.props.cellType === "code") {
|
if (this.props.cellType === "code") {
|
||||||
items = items.concat([
|
items = items.concat([
|
||||||
{
|
{
|
||||||
key: "Run",
|
key: "Run",
|
||||||
text: "Run",
|
text: "Run",
|
||||||
|
title: runTooltip,
|
||||||
|
disabled: isNotebookUntrusted,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
this.props.executeCell();
|
this.props.executeCell();
|
||||||
this.props.traceNotebookTelemetry(Action.NotebooksExecuteCellFromMenu, ActionModifiers.Mark);
|
this.props.traceNotebookTelemetry(Action.NotebooksExecuteCellFromMenu, ActionModifiers.Mark);
|
||||||
@ -223,6 +228,7 @@ const makeMapStateToProps = (state: AppState, ownProps: ComponentProps): ((state
|
|||||||
cellIdAbove,
|
cellIdAbove,
|
||||||
cellIdBelow,
|
cellIdBelow,
|
||||||
hasCodeOutput: cellType === "code" && NotebookUtil.hasCodeCellOutput(cell as ImmutableCodeCell),
|
hasCodeOutput: cellType === "code" && NotebookUtil.hasCodeCellOutput(cell as ImmutableCodeCell),
|
||||||
|
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, ownProps.contentRef),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`PromptContent renders for busy status 1`] = `
|
||||||
|
<div
|
||||||
|
className="greyStopButton"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"left": 0,
|
||||||
|
"maxHeight": "100%",
|
||||||
|
"position": "sticky",
|
||||||
|
"top": 0,
|
||||||
|
"width": "100%",
|
||||||
|
"zIndex": 300,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CustomizedIconButton
|
||||||
|
ariaLabel="Stop cell execution"
|
||||||
|
className="runCellButton"
|
||||||
|
iconProps={
|
||||||
|
Object {
|
||||||
|
"iconName": "CircleStopSolid",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"position": "absolute",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title="Stop cell execution"
|
||||||
|
/>
|
||||||
|
<StyledSpinnerBase
|
||||||
|
size={3}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"paddingTop": 5,
|
||||||
|
"position": "absolute",
|
||||||
|
"width": "100%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`PromptContent renders when hovered 1`] = `
|
||||||
|
<div
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<CustomizedIconButton
|
||||||
|
ariaLabel="Run cell"
|
||||||
|
className="runCellButton"
|
||||||
|
iconProps={
|
||||||
|
Object {
|
||||||
|
"iconName": "MSNVideosSolid",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title="Run cell"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -4,6 +4,7 @@ import Immutable from "immutable";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Dispatch } from "redux";
|
import { Dispatch } from "redux";
|
||||||
|
import { NotebookUtil } from "../../../NotebookUtil";
|
||||||
|
|
||||||
interface ComponentProps {
|
interface ComponentProps {
|
||||||
contentRef: ContentRef;
|
contentRef: ContentRef;
|
||||||
@ -14,6 +15,7 @@ interface StateProps {
|
|||||||
cellMap: Immutable.Map<string, any>;
|
cellMap: Immutable.Map<string, any>;
|
||||||
cellOrder: Immutable.List<string>;
|
cellOrder: Immutable.List<string>;
|
||||||
focusedCell?: string | null;
|
focusedCell?: string | null;
|
||||||
|
isNotebookUntrusted: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
@ -59,8 +61,13 @@ export class KeyboardShortcuts extends React.Component<Props> {
|
|||||||
cellOrder,
|
cellOrder,
|
||||||
focusedCell,
|
focusedCell,
|
||||||
cellMap,
|
cellMap,
|
||||||
|
isNotebookUntrusted,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
if (isNotebookUntrusted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let ctrlKeyPressed = e.ctrlKey;
|
let ctrlKeyPressed = e.ctrlKey;
|
||||||
// Allow cmd + enter (macOS) to operate like ctrl + enter
|
// Allow cmd + enter (macOS) to operate like ctrl + enter
|
||||||
if (process.platform === "darwin") {
|
if (process.platform === "darwin") {
|
||||||
@ -125,6 +132,7 @@ export const makeMapStateToProps = (_state: AppState, ownProps: ComponentProps)
|
|||||||
cellOrder,
|
cellOrder,
|
||||||
cellMap,
|
cellMap,
|
||||||
focusedCell,
|
focusedCell,
|
||||||
|
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ImmutableCodeCell, ImmutableNotebook } from "@nteract/commutable";
|
import { ImmutableCodeCell, ImmutableNotebook } from "@nteract/commutable";
|
||||||
|
import { AppState, selectors } from "@nteract/core";
|
||||||
import domtoimage from "dom-to-image";
|
import domtoimage from "dom-to-image";
|
||||||
import Html2Canvas from "html2canvas";
|
import Html2Canvas from "html2canvas";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
@ -11,6 +12,8 @@ import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentI
|
|||||||
export type FileType = "directory" | "file" | "notebook";
|
export type FileType = "directory" | "file" | "notebook";
|
||||||
// Utilities for notebooks
|
// Utilities for notebooks
|
||||||
export class NotebookUtil {
|
export class NotebookUtil {
|
||||||
|
public static UntrustedNotebookRunHint = "Please trust notebook first before running any code cells";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It's a notebook file if the filename ends with .ipynb.
|
* It's a notebook file if the filename ends with .ipynb.
|
||||||
*/
|
*/
|
||||||
@ -153,6 +156,16 @@ export class NotebookUtil {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static isNotebookUntrusted(state: AppState, contentRef: string): boolean {
|
||||||
|
const content = selectors.content(state, { contentRef });
|
||||||
|
if (content?.type === "notebook") {
|
||||||
|
const metadata = selectors.notebook.metadata(content.model);
|
||||||
|
return metadata.getIn(["untrusted"]) as boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find code cells with display
|
* Find code cells with display
|
||||||
* @param notebookObject
|
* @param notebookObject
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { SecurityWarningBar } from "./SecurityWarningBar";
|
||||||
|
|
||||||
|
describe("SecurityWarningBar", () => {
|
||||||
|
it("renders if notebook is untrusted", () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<SecurityWarningBar
|
||||||
|
contentRef={"contentRef"}
|
||||||
|
isNotebookUntrusted={true}
|
||||||
|
markNotebookAsTrusted={undefined}
|
||||||
|
saveNotebook={undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders if notebook is trusted", () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<SecurityWarningBar
|
||||||
|
contentRef={"contentRef"}
|
||||||
|
isNotebookUntrusted={false}
|
||||||
|
markNotebookAsTrusted={undefined}
|
||||||
|
saveNotebook={undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,93 @@
|
|||||||
|
import { MessageBar, MessageBarButton, MessageBarType } from "@fluentui/react";
|
||||||
|
import { actions, AppState } from "@nteract/core";
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Dispatch } from "redux";
|
||||||
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
|
|
||||||
|
export interface SecurityWarningBarPureProps {
|
||||||
|
contentRef: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SecurityWarningBarDispatchProps {
|
||||||
|
markNotebookAsTrusted: (contentRef: string) => void;
|
||||||
|
saveNotebook: (contentRef: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecurityWarningBarProps = SecurityWarningBarPureProps & StateProps & SecurityWarningBarDispatchProps;
|
||||||
|
|
||||||
|
interface SecurityWarningBarState {
|
||||||
|
isBarDismissed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SecurityWarningBar extends React.Component<SecurityWarningBarProps, SecurityWarningBarState> {
|
||||||
|
constructor(props: SecurityWarningBarProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isBarDismissed: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): JSX.Element {
|
||||||
|
return this.props.isNotebookUntrusted && !this.state.isBarDismissed ? (
|
||||||
|
<MessageBar
|
||||||
|
messageBarType={MessageBarType.warning}
|
||||||
|
isMultiline={false}
|
||||||
|
onDismiss={() => this.setState({ isBarDismissed: true })}
|
||||||
|
dismissButtonAriaLabel="Close"
|
||||||
|
actions={
|
||||||
|
<MessageBarButton
|
||||||
|
onClick={() => {
|
||||||
|
this.props.markNotebookAsTrusted(this.props.contentRef);
|
||||||
|
this.props.saveNotebook(this.props.contentRef);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Trust Notebook
|
||||||
|
</MessageBarButton>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
This notebook was downloaded from the public gallery. Running code cells from a notebook authored by someone
|
||||||
|
else may involve security risks.
|
||||||
|
</MessageBar>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateProps {
|
||||||
|
isNotebookUntrusted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InitialProps {
|
||||||
|
contentRef: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redux
|
||||||
|
const makeMapStateToProps = (state: AppState, initialProps: InitialProps) => {
|
||||||
|
const mapStateToProps = (state: AppState): StateProps => ({
|
||||||
|
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, initialProps.contentRef),
|
||||||
|
});
|
||||||
|
return mapStateToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeMapDispatchToProps = () => {
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch): SecurityWarningBarDispatchProps => {
|
||||||
|
return {
|
||||||
|
markNotebookAsTrusted: (contentRef: string) => {
|
||||||
|
return dispatch(
|
||||||
|
actions.deleteMetadataField({
|
||||||
|
contentRef,
|
||||||
|
field: "untrusted",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
saveNotebook: (contentRef: string) => dispatch(actions.save({ contentRef })),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return mapDispatchToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(makeMapStateToProps, makeMapDispatchToProps)(SecurityWarningBar);
|
@ -0,0 +1,22 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`SecurityWarningBar renders if notebook is trusted 1`] = `<Fragment />`;
|
||||||
|
|
||||||
|
exports[`SecurityWarningBar renders if notebook is untrusted 1`] = `
|
||||||
|
<StyledMessageBar
|
||||||
|
actions={
|
||||||
|
<CustomizedMessageBarButton
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
Trust Notebook
|
||||||
|
</CustomizedMessageBarButton>
|
||||||
|
}
|
||||||
|
dismissButtonAriaLabel="Close"
|
||||||
|
isMultiline={false}
|
||||||
|
messageBarType={5}
|
||||||
|
onDismiss={[Function]}
|
||||||
|
>
|
||||||
|
|
||||||
|
This notebook was downloaded from the public gallery. Running code cells from a notebook authored by someone else may involve security risks.
|
||||||
|
</StyledMessageBar>
|
||||||
|
`;
|
@ -24,6 +24,7 @@ import * as CdbActions from "../Notebook/NotebookComponent/actions";
|
|||||||
import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter";
|
import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter";
|
||||||
import { CdbAppState, SnapshotRequest } from "../Notebook/NotebookComponent/types";
|
import { CdbAppState, SnapshotRequest } from "../Notebook/NotebookComponent/types";
|
||||||
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
||||||
|
import { NotebookUtil } from "../Notebook/NotebookUtil";
|
||||||
import { useNotebook } from "../Notebook/useNotebook";
|
import { useNotebook } from "../Notebook/useNotebook";
|
||||||
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
|
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
|
||||||
|
|
||||||
@ -87,11 +88,13 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
|
|
||||||
protected getTabsButtons(): CommandButtonComponentProps[] {
|
protected getTabsButtons(): CommandButtonComponentProps[] {
|
||||||
const availableKernels = NotebookTabV2.clientManager.getAvailableKernelSpecs();
|
const availableKernels = NotebookTabV2.clientManager.getAvailableKernelSpecs();
|
||||||
|
const isNotebookUntrusted = this.notebookComponentAdapter.isNotebookUntrusted();
|
||||||
|
|
||||||
|
const runBtnTooltip = isNotebookUntrusted ? NotebookUtil.UntrustedNotebookRunHint : undefined;
|
||||||
|
|
||||||
const saveLabel = "Save";
|
const saveLabel = "Save";
|
||||||
const copyToLabel = "Copy to ...";
|
const copyToLabel = "Copy to ...";
|
||||||
const publishLabel = "Publish to gallery";
|
const publishLabel = "Publish to gallery";
|
||||||
const workspaceLabel = "No Workspace";
|
|
||||||
const kernelLabel = "No Kernel";
|
const kernelLabel = "No Kernel";
|
||||||
const runLabel = "Run";
|
const runLabel = "Run";
|
||||||
const runActiveCellLabel = "Run Active Cell";
|
const runActiveCellLabel = "Run Active Cell";
|
||||||
@ -108,8 +111,6 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
const copyLabel = "Copy";
|
const copyLabel = "Copy";
|
||||||
const cutLabel = "Cut";
|
const cutLabel = "Cut";
|
||||||
const pasteLabel = "Paste";
|
const pasteLabel = "Paste";
|
||||||
const undoLabel = "Undo";
|
|
||||||
const redoLabel = "Redo";
|
|
||||||
const cellCodeType = "code";
|
const cellCodeType = "code";
|
||||||
const cellMarkdownType = "markdown";
|
const cellMarkdownType = "markdown";
|
||||||
const cellRawType = "raw";
|
const cellRawType = "raw";
|
||||||
@ -190,9 +191,10 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
this.traceTelemetry(Action.ExecuteCell);
|
this.traceTelemetry(Action.ExecuteCell);
|
||||||
},
|
},
|
||||||
commandButtonLabel: runLabel,
|
commandButtonLabel: runLabel,
|
||||||
|
tooltipText: runBtnTooltip,
|
||||||
ariaLabel: runLabel,
|
ariaLabel: runLabel,
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
disabled: false,
|
disabled: isNotebookUntrusted,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
iconSrc: RunIcon,
|
iconSrc: RunIcon,
|
||||||
|
@ -243,6 +243,11 @@ export function downloadItem(
|
|||||||
const notebook = JSON.parse(response.data) as Notebook;
|
const notebook = JSON.parse(response.data) as Notebook;
|
||||||
removeNotebookViewerLink(notebook, data.newCellId);
|
removeNotebookViewerLink(notebook, data.newCellId);
|
||||||
|
|
||||||
|
if (!data.isSample) {
|
||||||
|
const metadata = notebook.metadata as { [name: string]: unknown };
|
||||||
|
metadata.untrusted = true;
|
||||||
|
}
|
||||||
|
|
||||||
await container.importAndOpenContent(data.name, JSON.stringify(notebook));
|
await container.importAndOpenContent(data.name, JSON.stringify(notebook));
|
||||||
logConsoleInfo(`Successfully downloaded ${name} to My Notebooks`);
|
logConsoleInfo(`Successfully downloaded ${name} to My Notebooks`);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user