forked from p15670423/monkey
Added ScoutSuite UI code
This commit is contained in:
parent
4440027699
commit
c66cb11e79
|
@ -1,9 +1,10 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from mongoengine import DateTimeField, Document, StringField, EmbeddedDocumentListField
|
from mongoengine import Document, EmbeddedDocumentListField
|
||||||
|
|
||||||
from monkey_island.cc.models.zero_trust.event import Event
|
from monkey_island.cc.models.zero_trust.event import Event
|
||||||
|
|
||||||
|
|
||||||
class MonkeyFindingDetails(Document):
|
class MonkeyFindingDetails(Document):
|
||||||
"""
|
"""
|
||||||
This model represents additional information about monkey finding:
|
This model represents additional information about monkey finding:
|
||||||
|
|
|
@ -60,7 +60,7 @@ class ReportPageComponent extends AuthComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
getZeroTrustReportFromServer = async () => {
|
getZeroTrustReportFromServer = async () => {
|
||||||
let ztReport = {findings: {}, principles: {}, pillars: {}};
|
let ztReport = {findings: {}, principles: {}, pillars: {}, scoutsuite_data: {}};
|
||||||
await this.authFetch('/api/report/zero_trust/findings')
|
await this.authFetch('/api/report/zero_trust/findings')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
@ -76,6 +76,11 @@ class ReportPageComponent extends AuthComponent {
|
||||||
.then(res => {
|
.then(res => {
|
||||||
ztReport.pillars = res;
|
ztReport.pillars = res;
|
||||||
});
|
});
|
||||||
|
await this.authFetch('/api/report/zero_trust/scoutsuite')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
ztReport.scoutsuite_data = res;
|
||||||
|
});
|
||||||
return ztReport
|
return ztReport
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ class ZeroTrustReportPageComponent extends AuthComponent {
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.report !== prevProps.report) {
|
if (this.props.report !== prevProps.report) {
|
||||||
this.setState(this.props.report)
|
this.setState(this.props.report)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,9 @@ class ZeroTrustReportPageComponent extends AuthComponent {
|
||||||
<SummarySection allMonkeysAreDead={this.state.allMonkeysAreDead} pillars={this.state.pillars}/>
|
<SummarySection allMonkeysAreDead={this.state.allMonkeysAreDead} pillars={this.state.pillars}/>
|
||||||
<PrinciplesSection principles={this.state.principles}
|
<PrinciplesSection principles={this.state.principles}
|
||||||
pillarsToStatuses={this.state.pillars.pillarsToStatuses}/>
|
pillarsToStatuses={this.state.pillars.pillarsToStatuses}/>
|
||||||
<FindingsSection pillarsToStatuses={this.state.pillars.pillarsToStatuses} findings={this.state.findings}/>
|
<FindingsSection pillarsToStatuses={this.state.pillars.pillarsToStatuses}
|
||||||
|
findings={this.state.findings}
|
||||||
|
scoutsuite_data={this.state.scoutsuite_data}/>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +59,8 @@ class ZeroTrustReportPageComponent extends AuthComponent {
|
||||||
stillLoadingDataFromServer() {
|
stillLoadingDataFromServer() {
|
||||||
return typeof this.state.findings === 'undefined'
|
return typeof this.state.findings === 'undefined'
|
||||||
|| typeof this.state.pillars === 'undefined'
|
|| typeof this.state.pillars === 'undefined'
|
||||||
|| typeof this.state.principles === 'undefined';
|
|| typeof this.state.principles === 'undefined'
|
||||||
|
|| typeof this.state.scoutsuite_data === 'undefined';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
const RULE_LEVELS = {
|
||||||
|
LEVEL_WARNING: 'warning',
|
||||||
|
LEVEL_DANGER: 'danger'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RULE_LEVELS
|
|
@ -0,0 +1,8 @@
|
||||||
|
const STATUSES = {
|
||||||
|
STATUS_UNEXECUTED: 'Unexecuted',
|
||||||
|
STATUS_PASSED: 'Passed',
|
||||||
|
STATUS_VERIFY: 'Verify',
|
||||||
|
STATUS_FAILED: 'Failed'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default STATUSES
|
|
@ -32,9 +32,15 @@ class FindingsSection extends Component {
|
||||||
insight as to what exactly happened during this test.
|
insight as to what exactly happened during this test.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<FindingsTable data={findingsByStatus[ZeroTrustStatuses.failed]} status={ZeroTrustStatuses.failed}/>
|
<FindingsTable data={findingsByStatus[ZeroTrustStatuses.failed]}
|
||||||
<FindingsTable data={findingsByStatus[ZeroTrustStatuses.verify]} status={ZeroTrustStatuses.verify}/>
|
scoutsuite_data={this.props.scoutsuite_data}
|
||||||
<FindingsTable data={findingsByStatus[ZeroTrustStatuses.passed]} status={ZeroTrustStatuses.passed}/>
|
status={ZeroTrustStatuses.failed}/>
|
||||||
|
<FindingsTable data={findingsByStatus[ZeroTrustStatuses.verify]}
|
||||||
|
scoutsuite_data={this.props.scoutsuite_data}
|
||||||
|
status={ZeroTrustStatuses.verify}/>
|
||||||
|
<FindingsTable data={findingsByStatus[ZeroTrustStatuses.passed]}
|
||||||
|
scoutsuite_data={this.props.scoutsuite_data}
|
||||||
|
status={ZeroTrustStatuses.passed}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,52 +4,59 @@ import PaginatedTable from '../common/PaginatedTable';
|
||||||
import * as PropTypes from 'prop-types';
|
import * as PropTypes from 'prop-types';
|
||||||
import PillarLabel from './PillarLabel';
|
import PillarLabel from './PillarLabel';
|
||||||
import EventsButton from './EventsButton';
|
import EventsButton from './EventsButton';
|
||||||
|
import ScoutSuiteRuleButton from './scoutsuite/ScoutSuiteRuleButton';
|
||||||
|
|
||||||
const EVENTS_COLUMN_MAX_WIDTH = 170;
|
const EVENTS_COLUMN_MAX_WIDTH = 250;
|
||||||
const PILLARS_COLUMN_MAX_WIDTH = 200;
|
const PILLARS_COLUMN_MAX_WIDTH = 200;
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
Header: 'Finding', accessor: 'test',
|
|
||||||
style: {'whiteSpace': 'unset'} // This enables word wrap
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Header: 'Events', id: 'events',
|
|
||||||
accessor: x => {
|
|
||||||
return <EventsButton finding_id={x.finding_id}
|
|
||||||
latest_events={x.latest_events}
|
|
||||||
oldest_events={x.oldest_events}
|
|
||||||
event_count={x.event_count}
|
|
||||||
exportFilename={'Events_' + x.test_key} />;
|
|
||||||
},
|
|
||||||
maxWidth: EVENTS_COLUMN_MAX_WIDTH
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Header: 'Pillars', id: 'pillars',
|
|
||||||
accessor: x => {
|
|
||||||
const pillars = x.pillars;
|
|
||||||
const pillarLabels = pillars.map((pillar) =>
|
|
||||||
<PillarLabel key={pillar.name} pillar={pillar.name} status={pillar.status}/>
|
|
||||||
);
|
|
||||||
return <div style={{textAlign: 'center'}}>{pillarLabels}</div>;
|
|
||||||
},
|
|
||||||
maxWidth: PILLARS_COLUMN_MAX_WIDTH,
|
|
||||||
style: {'whiteSpace': 'unset'}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
export class FindingsTable extends Component {
|
export class FindingsTable extends Component {
|
||||||
|
columns = [
|
||||||
|
{
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
Header: 'Finding', accessor: 'test',
|
||||||
|
style: {'whiteSpace': 'unset'} // This enables word wrap
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Header: 'Details', id: 'details',
|
||||||
|
accessor: x => {
|
||||||
|
if (x.type === 'scoutsuite_finding') {
|
||||||
|
return <ScoutSuiteRuleButton scoutsuite_rules={x.details.scoutsuite_rules}
|
||||||
|
scoutsuite_data={this.props.scoutsuite_data}/>;
|
||||||
|
} else if (x.type === 'monkey_finding') {
|
||||||
|
return <EventsButton finding_id={x.finding_id}
|
||||||
|
latest_events={x.details.latest_events}
|
||||||
|
oldest_events={x.details.oldest_events}
|
||||||
|
event_count={x.details.event_count}
|
||||||
|
exportFilename={'Events_' + x.test_key}/>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
maxWidth: EVENTS_COLUMN_MAX_WIDTH
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Header: 'Pillars', id: 'pillars',
|
||||||
|
accessor: x => {
|
||||||
|
const pillars = x.pillars;
|
||||||
|
const pillarLabels = pillars.map((pillar) =>
|
||||||
|
<PillarLabel key={pillar.name} pillar={pillar.name} status={pillar.status}/>
|
||||||
|
);
|
||||||
|
return <div style={{textAlign: 'center'}}>{pillarLabels}</div>;
|
||||||
|
},
|
||||||
|
maxWidth: PILLARS_COLUMN_MAX_WIDTH,
|
||||||
|
style: {'whiteSpace': 'unset'}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Fragment>
|
return <Fragment>
|
||||||
<h3>{<span style={{display: 'inline-block'}}><StatusLabel status={this.props.status} showText={true}/>
|
<h3>{<span style={{display: 'inline-block'}}><StatusLabel status={this.props.status} showText={true}/>
|
||||||
</span>} tests' findings</h3>
|
</span>} tests' findings</h3>
|
||||||
<PaginatedTable data={this.props.data} pageSize={10} columns={columns}/>
|
<PaginatedTable data={this.props.data} pageSize={10} columns={this.columns}/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React, {useState} from 'react';
|
||||||
|
import * as PropTypes from 'prop-types';
|
||||||
|
import '../../../../styles/components/scoutsuite/RuleDisplay.scss'
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
|
import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown';
|
||||||
|
import {faChevronUp} from '@fortawesome/free-solid-svg-icons/faChevronUp';
|
||||||
|
import ScoutSuiteDataParser from './ScoutSuiteDataParser';
|
||||||
|
import Collapse from '@kunukn/react-collapse';
|
||||||
|
import {faArrowRight} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
export default function ResourceDropdown(props) {
|
||||||
|
|
||||||
|
const [isCollapseOpen, setIsCollapseOpen] = useState(false);
|
||||||
|
|
||||||
|
function getResourceDropdown() {
|
||||||
|
return (
|
||||||
|
<div key={props.resource_path} className={classNames('collapse-item',
|
||||||
|
'resource-collapse', {'item--active': isCollapseOpen})}>
|
||||||
|
<button className={'btn-collapse'}
|
||||||
|
onClick={() => setIsCollapseOpen(!isCollapseOpen)}>
|
||||||
|
<span>
|
||||||
|
{props.resource_path}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<FontAwesomeIcon icon={isCollapseOpen ? faChevronDown : faChevronUp}/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<Collapse
|
||||||
|
className='collapse-comp'
|
||||||
|
isOpen={isCollapseOpen}
|
||||||
|
render={getResourceDropdownContents}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function replacePathDotsWithArrows(resourcePath) {
|
||||||
|
let path_vars = resourcePath.split('.')
|
||||||
|
let display_path = []
|
||||||
|
for(let i = 0; i < path_vars.length; i++){
|
||||||
|
display_path.push(path_vars[i])
|
||||||
|
if( i !== path_vars.length - 1) {
|
||||||
|
display_path.push(<FontAwesomeIcon icon={faArrowRight} />)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return display_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function prettyPrintJson(data) {
|
||||||
|
return JSON.stringify(data, null, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getResourceDropdownContents() {
|
||||||
|
let parser = new ScoutSuiteDataParser(props.scoutsuite_data.data.services);
|
||||||
|
return (
|
||||||
|
<div className={'resource-display'}>
|
||||||
|
<div>
|
||||||
|
<p className={'resource-path-title'}>Path:</p>
|
||||||
|
<p className={'resource-path-contents'}>{replacePathDotsWithArrows(props.resource_path)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className={'resource-value-title'}>Value:</p>
|
||||||
|
<pre className={'resource-value-json'}>{prettyPrintJson(parser.getValueAt(props.resource_path))}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getResourceDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceDropdown.propTypes = {
|
||||||
|
resource_path: PropTypes.object,
|
||||||
|
scoutsuite_data: PropTypes.object
|
||||||
|
};
|
|
@ -0,0 +1,56 @@
|
||||||
|
import React from 'react';
|
||||||
|
import * as PropTypes from 'prop-types';
|
||||||
|
import '../../../../styles/components/scoutsuite/RuleDisplay.scss'
|
||||||
|
import ResourceDropdown from './ResourceDropdown';
|
||||||
|
|
||||||
|
export default function RuleDisplay(props) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'scoutsuite-rule-display'}>
|
||||||
|
<div className={'description'}>
|
||||||
|
<h3>{props.rule.description}({props.rule.service})</h3>
|
||||||
|
</div>
|
||||||
|
<div className={'rationale'}>
|
||||||
|
<p>{props.rule.rationale}</p>
|
||||||
|
</div>
|
||||||
|
<div className={'checked-resources'}>
|
||||||
|
<p className={'checked-resources-title'}>Resources checked: </p>
|
||||||
|
<p>{props.rule.checked_items}</p>
|
||||||
|
</div>
|
||||||
|
<div className={'flagged-resources'}>
|
||||||
|
<p className={'checked-resources-title'}>Resources flagged: </p>
|
||||||
|
<p>{props.rule.flagged_items}</p>
|
||||||
|
</div>
|
||||||
|
{props.rule.references.length !== 0 ? getReferences() : ''}
|
||||||
|
{props.rule.items.length !== 0 ? getResources() : ''}
|
||||||
|
</div>);
|
||||||
|
|
||||||
|
function getReferences() {
|
||||||
|
let references = []
|
||||||
|
props.rule.references.forEach(reference => {
|
||||||
|
references.push(<a href={reference} className={'reference-link'} target={'_blank'}>{reference}</a>)
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div className={'reference-list'}>
|
||||||
|
<p className={'reference-list-title'}>References:</p>
|
||||||
|
{references}
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getResources() {
|
||||||
|
let resources = []
|
||||||
|
props.rule.items.forEach(item => {
|
||||||
|
resources.push(<ResourceDropdown resource_path={item} scoutsuite_data={props.scoutsuite_data}/>)
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div className={'reference-list'}>
|
||||||
|
<p className={'reference-list-title'}>Resources:</p>
|
||||||
|
{resources}
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RuleDisplay.propTypes = {
|
||||||
|
rule: PropTypes.object,
|
||||||
|
scoutsuite_data: PropTypes.object
|
||||||
|
};
|
|
@ -0,0 +1,58 @@
|
||||||
|
export default class ScoutSuiteDataParser {
|
||||||
|
constructor(runResults) {
|
||||||
|
this.runResults = runResults
|
||||||
|
}
|
||||||
|
|
||||||
|
getValueAt(path) {
|
||||||
|
return this.getValueAtRecursive(path, this.runResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
getValueAtRecursive(path, source) {
|
||||||
|
let value = source;
|
||||||
|
let current_path = path;
|
||||||
|
let key;
|
||||||
|
// iterate over each path elements
|
||||||
|
while (current_path) {
|
||||||
|
// check if there are more elements to the path
|
||||||
|
if (current_path.indexOf('.') != -1) {
|
||||||
|
key = current_path.substr(0, current_path.indexOf('.'));
|
||||||
|
}
|
||||||
|
// last element
|
||||||
|
else {
|
||||||
|
key = current_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// path containing an ".id"
|
||||||
|
if (key == 'id') {
|
||||||
|
let v = [];
|
||||||
|
let w;
|
||||||
|
for (let k in value) {
|
||||||
|
// process recursively
|
||||||
|
w = this.getValueAtRecursive(k + current_path.substr(current_path.indexOf('.'), current_path.length), value);
|
||||||
|
v = v.concat(
|
||||||
|
Object.values(w) // get values from array, otherwise it will be an array of key/values
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
// simple path, just return element in value
|
||||||
|
else {
|
||||||
|
value = value[key];
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error: ' + err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if there are more elements to process
|
||||||
|
if (current_path.indexOf('.') != -1) {
|
||||||
|
current_path = current_path.substr(current_path.indexOf('.') + 1, current_path.length);
|
||||||
|
}
|
||||||
|
// otherwise we're done
|
||||||
|
else {
|
||||||
|
current_path = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
import {Badge, Button} from 'react-bootstrap';
|
||||||
|
import * as PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
|
import {faList} from '@fortawesome/free-solid-svg-icons/faList';
|
||||||
|
import ScoutSuiteRuleModal from './ScoutSuiteRuleModal';
|
||||||
|
|
||||||
|
export default class ScoutSuiteRuleButton extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isModalOpen: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleModal = () => {
|
||||||
|
this.setState({isModalOpen: !this.state.isModalOpen});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ScoutSuiteRuleModal scoutsuite_rules={this.props.scoutsuite_rules}
|
||||||
|
scoutsuite_data={this.props.scoutsuite_data}
|
||||||
|
isModalOpen={this.state.isModalOpen}
|
||||||
|
hideCallback={this.toggleModal} />
|
||||||
|
<div className="text-center" style={{'display': 'grid'}}>
|
||||||
|
<Button variant={'monkey-info'} size={'lg'} onClick={this.toggleModal}>
|
||||||
|
<FontAwesomeIcon icon={faList}/> ScoutSuite rules {this.createRuleCountBadge()}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
|
||||||
|
createRuleCountBadge() {
|
||||||
|
const ruleCount = this.props.scoutsuite_rules.length > 9 ? '9+' : this.props.scoutsuite_rules.length;
|
||||||
|
return <Badge variant={'monkey-info-light'}>{ruleCount}</Badge>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScoutSuiteRuleButton.propTypes = {
|
||||||
|
scoutsuite_rules: PropTypes.array,
|
||||||
|
scoutsuite_data: PropTypes.object
|
||||||
|
};
|
|
@ -0,0 +1,58 @@
|
||||||
|
import React, {useState} from 'react';
|
||||||
|
import {Modal} from 'react-bootstrap';
|
||||||
|
import * as PropTypes from 'prop-types';
|
||||||
|
import Pluralize from 'pluralize';
|
||||||
|
import ScoutSuiteSingleRuleDropdown from './ScoutSuiteSingleRuleDropdown';
|
||||||
|
import '../../../../styles/components/scoutsuite/RuleModal.scss';
|
||||||
|
|
||||||
|
|
||||||
|
export default function ScoutSuiteRuleModal(props) {
|
||||||
|
const [openRuleId, setOpenRuleId] = useState(null)
|
||||||
|
|
||||||
|
function toggleRuleDropdown(ruleId) {
|
||||||
|
if (openRuleId === ruleId) {
|
||||||
|
setOpenRuleId(null);
|
||||||
|
} else {
|
||||||
|
setOpenRuleId(ruleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRuleDropdowns() {
|
||||||
|
let dropdowns = [];
|
||||||
|
props.scoutsuite_rules.forEach(rule => {
|
||||||
|
let dropdown = (<ScoutSuiteSingleRuleDropdown isCollapseOpen={openRuleId === rule.description}
|
||||||
|
toggleCallback={() => toggleRuleDropdown(rule.description)}
|
||||||
|
rule={rule}
|
||||||
|
scoutsuite_data={props.scoutsuite_data}/>)
|
||||||
|
dropdowns.push(dropdown)
|
||||||
|
});
|
||||||
|
return dropdowns;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal show={props.isModalOpen} onHide={() => props.hideCallback()} className={'scoutsuite-rule-modal'}>
|
||||||
|
<Modal.Body>
|
||||||
|
<h3>
|
||||||
|
<div className="text-center">ScoutSuite rules</div>
|
||||||
|
</h3>
|
||||||
|
<hr/>
|
||||||
|
<p>
|
||||||
|
There {Pluralize('is', props.scoutsuite_rules.length)} {
|
||||||
|
<div className={'badge badge-primary'}>{props.scoutsuite_rules.length}</div>
|
||||||
|
} ScoutSuite {Pluralize('rule', props.scoutsuite_rules.length)} associated with finding.
|
||||||
|
</p>
|
||||||
|
{renderRuleDropdowns()}
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ScoutSuiteRuleModal.propTypes = {
|
||||||
|
isModalOpen: PropTypes.bool,
|
||||||
|
scoutsuite_rules: PropTypes.array,
|
||||||
|
scoutsuite_data: PropTypes.object,
|
||||||
|
hideCallback: PropTypes.func
|
||||||
|
};
|
|
@ -0,0 +1,90 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Collapse from '@kunukn/react-collapse';
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
|
||||||
|
import {faChevronUp} from '@fortawesome/free-solid-svg-icons/faChevronUp'
|
||||||
|
import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown'
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import * as PropTypes from 'prop-types';
|
||||||
|
import RULE_LEVELS from '../../common/consts/ScoutSuiteConsts/RuleLevels';
|
||||||
|
import STATUSES from '../../common/consts/StatusConsts';
|
||||||
|
import {faCheckCircle, faCircle, faExclamationCircle} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import RuleDisplay from './RuleDisplay';
|
||||||
|
|
||||||
|
export default function ScoutSuiteSingleRuleDropdown(props) {
|
||||||
|
|
||||||
|
function getRuleCollapse() {
|
||||||
|
return (
|
||||||
|
<div key={props.rule.description} className={classNames('collapse-item',
|
||||||
|
'rule-collapse', {'item--active': props.isCollapseOpen})}>
|
||||||
|
<button className={classNames('btn-collapse', getDropdownClass())}
|
||||||
|
onClick={props.toggleCallback}>
|
||||||
|
<span>
|
||||||
|
<FontAwesomeIcon icon={getRuleIcon()} />
|
||||||
|
{props.rule.description}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<FontAwesomeIcon icon={props.isCollapseOpen ? faChevronDown : faChevronUp}/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<Collapse
|
||||||
|
className='collapse-comp'
|
||||||
|
isOpen={props.isCollapseOpen}
|
||||||
|
render={renderRule}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRuleIcon() {
|
||||||
|
let ruleStatus = getRuleStatus()
|
||||||
|
switch(ruleStatus) {
|
||||||
|
case STATUSES.STATUS_PASSED:
|
||||||
|
return faCheckCircle;
|
||||||
|
case STATUSES.STATUS_VERIFY:
|
||||||
|
return faExclamationCircle;
|
||||||
|
case STATUSES.STATUS_FAILED:
|
||||||
|
return faExclamationCircle;
|
||||||
|
case STATUSES.STATUS_UNEXECUTED:
|
||||||
|
return faCircle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDropdownClass(){
|
||||||
|
let ruleStatus = getRuleStatus()
|
||||||
|
switch(ruleStatus) {
|
||||||
|
case STATUSES.STATUS_PASSED:
|
||||||
|
return "collapse-success";
|
||||||
|
case STATUSES.STATUS_VERIFY:
|
||||||
|
return "collapse-warning";
|
||||||
|
case STATUSES.STATUS_FAILED:
|
||||||
|
return "collapse-danger";
|
||||||
|
case STATUSES.STATUS_UNEXECUTED:
|
||||||
|
return "collapse-default";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRuleStatus(){
|
||||||
|
if(props.rule.checked_items === 0) {
|
||||||
|
return STATUSES.STATUS_UNEXECUTED
|
||||||
|
} else if (props.rule.items.length === 0) {
|
||||||
|
return STATUSES.STATUS_PASSED
|
||||||
|
} else if (props.rule.level === RULE_LEVELS.LEVEL_WARNING) {
|
||||||
|
return STATUSES.STATUS_VERIFY
|
||||||
|
} else {
|
||||||
|
return STATUSES.STATUS_FAILED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRule() {
|
||||||
|
return <RuleDisplay rule={props.rule} scoutsuite_data={props.scoutsuite_data}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
return getRuleCollapse();
|
||||||
|
}
|
||||||
|
|
||||||
|
ScoutSuiteSingleRuleDropdown.propTypes = {
|
||||||
|
isCollapseOpen: PropTypes.bool,
|
||||||
|
rule: PropTypes.object,
|
||||||
|
scoutsuite_data: PropTypes.object,
|
||||||
|
toggleCallback: PropTypes.func
|
||||||
|
};
|
|
@ -12,6 +12,7 @@
|
||||||
@import 'components/PreviewPane';
|
@import 'components/PreviewPane';
|
||||||
@import 'components/AdvancedMultiSelect';
|
@import 'components/AdvancedMultiSelect';
|
||||||
@import 'components/particle-component/ParticleBackground';
|
@import 'components/particle-component/ParticleBackground';
|
||||||
|
@import 'components/scoutsuite/ResourceDropdown';
|
||||||
|
|
||||||
|
|
||||||
// Define custom elements after bootstrap import
|
// Define custom elements after bootstrap import
|
||||||
|
|
|
@ -5,6 +5,7 @@ $disabled-color: #f2f2f2;
|
||||||
$info-color: #ade3eb;
|
$info-color: #ade3eb;
|
||||||
$default-color: #8c8c8c;
|
$default-color: #8c8c8c;
|
||||||
$warning-color: #ffe28d;
|
$warning-color: #ffe28d;
|
||||||
|
$success-color: #adf6a9;
|
||||||
|
|
||||||
.collapse-item button {
|
.collapse-item button {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
|
@ -39,6 +40,10 @@ $warning-color: #ffe28d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.collapse-success {
|
||||||
|
background-color: $success-color !important;
|
||||||
|
}
|
||||||
|
|
||||||
.collapse-danger {
|
.collapse-danger {
|
||||||
background-color: $danger-color !important;
|
background-color: $danger-color !important;
|
||||||
}
|
}
|
||||||
|
@ -99,3 +104,7 @@ $warning-color: #ffe28d;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
min-width: 6em;
|
min-width: 6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rule-collapse svg{
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
.resource-display {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-display .resource-value-json {
|
||||||
|
background-color: $gray-200;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-display .resource-path-contents svg {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-display .resource-value-title,
|
||||||
|
.resource-display .resource-path-title {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
.scoutsuite-rule-display .description h3{
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scoutsuite-rule-display p{
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scoutsuite-rule-display .checked-resources-title,
|
||||||
|
.scoutsuite-rule-display .flagged-resources-title,
|
||||||
|
.scoutsuite-rule-display .reference-list-title{
|
||||||
|
font-weight: 500;
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scoutsuite-rule-display .reference-list a {
|
||||||
|
display: block;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
.scoutsuite-rule-modal .modal-dialog{
|
||||||
|
max-width: 1000px;
|
||||||
|
top: 0;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
Loading…
Reference in New Issue