Merge pull request #132 from BloodHoundAD/UI-Update

Object Properties Update
master
Rohan Vazarkar 2017-10-10 03:54:19 -04:00 committed by GitHub
commit 60a68c4a54
20 changed files with 2862 additions and 2605 deletions

View File

@ -16,7 +16,6 @@
<script src="node_modules/linkurious/dist/plugins.js" type="text/javascript"></script> <script src="node_modules/linkurious/dist/plugins.js" type="text/javascript"></script>
<script src="node_modules/dagre/dist/dagre.min.js" type="text/javascript"></script> <script src="node_modules/dagre/dist/dagre.min.js" type="text/javascript"></script>
<script src="node_modules/bootstrap-3-typeahead/bootstrap3-typeahead.min.js" type="text/javascript"></script> <script src="node_modules/bootstrap-3-typeahead/bootstrap3-typeahead.min.js" type="text/javascript"></script>
<script src="src/js/papaparse.min.js" type="text/javascript"></script>
<script src="src/js/simple-slider.min.js" type="text/javascript"></script> <script src="src/js/simple-slider.min.js" type="text/javascript"></script>
<link type="text/css" rel="stylesheet" href="src/css/simple-slider.css"> <link type="text/css" rel="stylesheet" href="src/css/simple-slider.css">
<link type="text/css" rel="stylesheet" href="src/css/simple-slider-volume.css"> <link type="text/css" rel="stylesheet" href="src/css/simple-slider-volume.css">

View File

@ -28,7 +28,7 @@
}, },
"babel": { "babel": {
"presets": [ "presets": [
"es2015", "env",
"stage-0", "stage-0",
"react" "react"
] ]
@ -38,7 +38,7 @@
"babel-core": "^6.22.1", "babel-core": "^6.22.1",
"babel-loader": "^7.0.0", "babel-loader": "^7.0.0",
"babel-polyfill": "^6.22.0", "babel-polyfill": "^6.22.0",
"babel-preset-es2015": "^6.22.0", "babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.22.0", "babel-preset-react": "^6.22.0",
"babel-preset-stage-0": "^6.22.0", "babel-preset-stage-0": "^6.22.0",
"concurrently": "^3.1.0", "concurrently": "^3.1.0",
@ -56,15 +56,16 @@
"configstore": "^3.1.0", "configstore": "^3.1.0",
"dagre": "^0.7.4", "dagre": "^0.7.4",
"eventemitter2": "^4.1.0", "eventemitter2": "^4.1.0",
"fast-csv": "^2.4.1",
"jquery": "^3.2.1", "jquery": "^3.2.1",
"linkurious": "^1.5.1", "linkurious": "^1.5.1",
"mustache": "^2.2.1", "mustache": "^2.2.1",
"neo4j-driver": "^1.3.0", "neo4j-driver": "^1.4.1",
"prop-types": "^15.5.10",
"react": "^15.4.2", "react": "^15.4.2",
"react-bootstrap": "^0.31.0", "react-bootstrap": "^0.31.0",
"react-dom": "^15.4.2", "react-dom": "^15.4.2",
"react-if": "^2.1.0", "react-if": "^2.1.0",
"react-transition-group": "^1.1.3" "react-transition-group": "^1.1.3",
"unzipper": "^0.8.9"
} }
} }

View File

@ -2,46 +2,46 @@ import React, { Component } from 'react';
import { Alert } from 'react-bootstrap'; import { Alert } from 'react-bootstrap';
export default class GenericAlert extends Component { export default class GenericAlert extends Component {
constructor(){ constructor(){
super() super();
this.state = { this.state = {
visible: false, visible: false,
text: "No data returned from query", text: "No data returned from query",
timeout: null timeout: null
} };
emitter.on('showAlert', this._show.bind(this)) emitter.on('showAlert', this._show.bind(this));
emitter.on('hideAlert', this._dismiss.bind(this)) emitter.on('hideAlert', this._dismiss.bind(this));
} }
_dismiss(){ _dismiss(){
this.setState({visible: false}); this.setState({visible: false});
} }
_show(val){ _show(val){
clearTimeout(this.state.timeout) clearTimeout(this.state.timeout);
var t = setTimeout(function(){ var t = setTimeout(function(){
this._dismiss() this._dismiss();
}.bind(this), 2500) }.bind(this), 2500);
this.setState({
visible: true,
text: val,
timeout: t
})
} this.setState({
visible: true,
text: val,
timeout: t
});
}
render() { render() {
if (this.state.visible){ if (this.state.visible){
return ( return (
<Alert className="alertdiv" bsStyle="danger" onDismiss={this._dismiss.bind(this)}> <Alert className="alertdiv" bsStyle="danger" onDismiss={this._dismiss.bind(this)}>
{this.state.text} {this.state.text}
</Alert> </Alert>
) );
}else{ }else{
return null return null;
} }
} }
} }

View File

@ -1,280 +1,285 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
export default class Login extends Component { export default class Login extends Component {
constructor(){ constructor(){
super(); super();
this.state = { this.state = {
url: "", url: "",
icon: null, icon: null,
loginEnabled: false, loginEnabled: false,
user: "", user: "",
password: "", password: "",
loginInProgress: false, loginInProgress: false,
save: false save: false
} };
} }
checkDBPresence(){ componentWillMount() {
var url = this.state.url; var c = conf.get('databaseInfo');
var icon = this.state.icon; if (typeof c !== 'undefined'){
var jicon = jQuery(icon) this.setState({
var btn = jQuery(this.refs.loginButton) url: c.url,
user: c.user,
password: c.password,
save: true
});
}
}
if (url === ""){ componentDidMount() {
return; jQuery(this.refs.password).tooltip({
} placement : 'right',
title: '',
container: 'body',
trigger: 'manual',
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner tooltip-inner-custom"></div></div>'
});
this.setIcon();
if (this.state.password !== ""){
this.checkDBCreds();
}
}
jQuery(this.refs.urlspinner).toggle(true) setIcon(){
var icon = jQuery(this.refs.urlspinner);
icon.tooltip({
placement : 'right',
title: '',
container: 'body',
delay: {show: 200, hide: 0},
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner tooltip-inner-custom"></div></div>'
});
icon.toggle(false);
this.setState({icon: jQuery(this.refs.urlspinner)});
}
url = url.replace(/\/$/, ""); checkDBPresence(){
var url = this.state.url;
var icon = this.state.icon;
var jicon = jQuery(icon);
var btn = jQuery(this.refs.loginButton);
if (!url.includes(':')){ if (url === ""){
url = url + ':7687' return;
} }
if (!url.startsWith('bolt://')){ jQuery(this.refs.urlspinner).toggle(true);
url = 'bolt://' + url
}
icon.removeClass(); url = url.replace(/\/$/, "");
icon.addClass("fa fa-spinner fa-spin form-control-feedback");
icon.toggle(true);
var driver = neo4j.driver(url, neo4j.auth.basic("",""), {encrypted:'ENCRYPTION_ON'})
var session = driver.session();
driver.onCompleted = function(){ if (!url.includes(':')){
session.close() url = url + ':7687';
driver.close() }
}
driver.onError = function(error){
if (error.message.includes("authentication failure")){
icon.removeClass();
icon.addClass("fa fa-check-circle green-icon-color form-control-feedback");
this.setState({loginEnabled: true, url: url})
}else{
icon.removeClass();
icon.addClass("fa fa-times-circle red-icon-color form-control-feedback");
icon.attr('data-original-title', 'No database found')
.tooltip('fixTitle')
.tooltip('show')
this.setState({
loginInProgress: false,
loginEnabled: false
})
}
session.close()
driver.close()
}.bind(this)
session.run("return 1")
}
checkDBCreds(){ if (!url.startsWith('bolt://')){
if (this.state.loginInProgress){ url = 'bolt://' + url;
return; }
}
this.setState({
loginInProgress: true,
loginEnabled: false
})
var btn = jQuery(this.refs.loginButton) icon.removeClass();
var pwf = jQuery(this.refs.password) icon.addClass("fa fa-spinner fa-spin form-control-feedback");
icon.toggle(true);
var driver = neo4j.driver(url, neo4j.auth.basic("",""), {encrypted:'ENCRYPTION_ON'});
var session = driver.session();
var driver = neo4j.driver(this.state.url, neo4j.auth.basic(this.state.user, this.state.password)) driver.onCompleted = function(){
driver.onError = function(error){ session.close();
console.log(error) driver.close();
if (error.message.includes("authentication failure")){ };
btn.removeClass('activate'); driver.onError = function(error){
this.setState({ if (error.message.includes("authentication failure")){
loginInProgress: false, icon.removeClass();
loginEnabled: true icon.addClass("fa fa-check-circle green-icon-color form-control-feedback");
}) this.setState({loginEnabled: true, url: url});
pwf.attr('data-original-title', 'Invalid username or password') }else{
.tooltip('fixTitle') icon.removeClass();
.tooltip('show') icon.addClass("fa fa-times-circle red-icon-color form-control-feedback");
}else if (error.message.includes("too many times in a row")){ icon.attr('data-original-title', 'No database found')
btn.removeClass('activate'); .tooltip('fixTitle')
this.setState({ .tooltip('show');
loginInProgress: false, this.setState({
loginEnabled: true loginInProgress: false,
}) loginEnabled: false
pwf.attr('data-original-title', 'Too many authentication attempts, please wait') });
.tooltip('fixTitle') }
.tooltip('show') session.close();
}else if (error.toString().includes('ECONNREFUSED')){ driver.close();
var icon = this.state.icon }.bind(this);
icon.toggle('true') session.run("return 1");
icon.removeClass(); }
icon.addClass("fa fa-times-circle red-icon-color form-control-feedback");
icon.attr('data-original-title', 'No database found')
.tooltip('fixTitle')
.tooltip('show')
this.setState({
loginInProgress: false,
loginEnabled: false
})
}
driver.close()
}.bind(this)
var session = driver.session();
session.run('MATCH (n) RETURN (n) LIMIT 1')
.subscribe({
onError: function(error){
btn.removeClass('activate');
var url = this.state.url.replace('bolt://','http://').replace('7687','7474')
if (error.fields && error.fields[0].code === "Neo.ClientError.Security.CredentialsExpired"){
pwf.attr('data-original-title', 'Credentials need to be changed from the neo4j browser first. Go to {} and change them.'.format(url))
.tooltip('fixTitle')
.tooltip('show')
this.setState({
loginInProgress: false,
loginEnabled: true
})
}
}.bind(this),
onNext: function(){
}, checkDBCreds(){
onCompleted: function(){ if (this.state.loginInProgress){
btn.toggleClass('activate'); return;
btn.removeClass('btn-default') }
btn.addClass('btn-success') this.setState({
btn.html('Success!') loginInProgress: true,
this.setState({ loginEnabled: false
loginInProgress: false });
})
var dbinfo = { var btn = jQuery(this.refs.loginButton);
url: this.state.url, var pwf = jQuery(this.refs.password);
user: this.state.user,
password: this.state.password
}
if (this.state.save){
conf.set('databaseInfo',dbinfo)
}
appStore.databaseInfo = dbinfo; var driver = neo4j.driver(this.state.url, neo4j.auth.basic(this.state.user, this.state.password));
driver.onError = function(error){
jQuery(this.refs.password).tooltip('hide') console.log(error);
jQuery(this.refs.urlspinner).tooltip('hide') if (error.message.includes("authentication failure")){
setTimeout(function(){ btn.removeClass('activate');
jQuery(this.refs.outer).fadeOut(400, function(){ this.setState({
renderEmit.emit('login'); loginInProgress: false,
}); loginEnabled: true
}.bind(this), 1500) });
driver.close() pwf.attr('data-original-title', 'Invalid username or password')
global.driver = neo4j.driver(this.state.url, neo4j.auth.basic(this.state.user, this.state.password)) .tooltip('fixTitle')
}.bind(this) .tooltip('show');
}) }else if (error.message.includes("too many times in a row")){
btn.removeClass('activate');
this.setState({
loginInProgress: false,
loginEnabled: true
});
pwf.attr('data-original-title', 'Too many authentication attempts, please wait')
.tooltip('fixTitle')
.tooltip('show');
}else if (error.toString().includes('ECONNREFUSED')){
var icon = this.state.icon;
icon.toggle('true');
icon.removeClass();
icon.addClass("fa fa-times-circle red-icon-color form-control-feedback");
icon.attr('data-original-title', 'No database found')
.tooltip('fixTitle')
.tooltip('show');
this.setState({
loginInProgress: false,
loginEnabled: false
});
}
driver.close();
}.bind(this);
var session = driver.session();
session.run('MATCH (n) RETURN (n) LIMIT 1')
.subscribe({
onError: function(error){
btn.removeClass('activate');
var url = this.state.url.replace('bolt://','http://').replace('7687','7474');
if (error.fields && error.fields[0].code === "Neo.ClientError.Security.CredentialsExpired"){
pwf.attr('data-original-title', 'Credentials need to be changed from the neo4j browser first. Go to {} and change them.'.format(url))
.tooltip('fixTitle')
.tooltip('show');
this.setState({
loginInProgress: false,
loginEnabled: true
});
}
}.bind(this),
onNext: function(){
btn.toggleClass('activate'); },
onCompleted: function(){
btn.toggleClass('activate');
btn.removeClass('btn-default');
btn.addClass('btn-success');
btn.html('Success!');
this.setState({
loginInProgress: false
});
} var dbinfo = {
url: this.state.url,
user: this.state.user,
password: this.state.password
};
if (this.state.save){
conf.set('databaseInfo',dbinfo);
}
componentWillMount() { appStore.databaseInfo = dbinfo;
var c = conf.get('databaseInfo')
if (typeof c !== 'undefined'){ jQuery(this.refs.password).tooltip('hide');
this.setState({ jQuery(this.refs.urlspinner).tooltip('hide');
url: c.url, setTimeout(function(){
user: c.user, jQuery(this.refs.outer).fadeOut(400, function(){
password: c.password, renderEmit.emit('login');
save: true });
}) }.bind(this), 1500);
} driver.close();
} global.driver = neo4j.driver(this.state.url, neo4j.auth.basic(this.state.user, this.state.password));
}.bind(this)
});
componentDidMount() { btn.toggleClass('activate');
jQuery(this.refs.password).tooltip({
placement : 'right',
title: '',
container: 'body',
trigger: 'manual',
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner tooltip-inner-custom"></div></div>'
})
this.setState({icon: jQuery(this.refs.urlspinner)})
var icon = jQuery(this.refs.urlspinner)
icon.tooltip({
placement : 'right',
title: '',
container: 'body',
delay: {show: 200, hide: 0},
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner tooltip-inner-custom"></div></div>'
})
icon.toggle(false)
if (this.state.password !== ""){
this.checkDBCreds();
}
}
_saveChange(event) { }
this.setState({save: event.target.checked});
}
_urlChanged(event){ _saveChange(event) {
this.setState({url: event.target.value}) this.setState({save: event.target.checked});
} }
_userChanged(event){ _urlChanged(event){
this.setState({user: event.target.value}) this.setState({url: event.target.value});
jQuery(this.refs.password).tooltip('hide') }
}
_passChanged(event){ _userChanged(event){
this.setState({password: event.target.value}) this.setState({user: event.target.value});
jQuery(this.refs.password).tooltip('hide') jQuery(this.refs.password).tooltip('hide');
} }
_triggerLogin(e){ _passChanged(event){
var key = e.keyCode ? e.keyCode : e.which this.setState({password: event.target.value});
jQuery(this.refs.password).tooltip('hide');
}
if (key === 13){ _triggerLogin(e){
this.checkDBCreds() var key = e.keyCode ? e.keyCode : e.which;
}
}
render() { if (key === 13){
return ( this.checkDBCreds();
<div className="loginwindow"> }
<div id="loginpanel" ref="outer"> }
<img src="src/img/logo-white-transparent-full.png" />
<div className="text-center"> render() {
<span>Log in to Neo4j Database</span> return (
</div> <div className="loginwindow">
<form> <div id="loginpanel" ref="outer">
<div className="form-group has-feedback"> <img src="src/img/logo-white-transparent-full.png" />
<div className="input-group"> <div className="text-center">
<span className="input-group-addon" id="dburladdon"> <span>Log in to Neo4j Database</span>
Database URL </div>
</span> <form>
<input ref="url" onFocus={function(){jQuery(this.state.icon).tooltip('hide');}.bind(this)} onBlur={this.checkDBPresence.bind(this)} onChange={this._urlChanged.bind(this)} type="text" className="form-control" value={this.state.url} placeholder="bolt://localhost:7687" aria-describedby="dburladdon" /> <div className="form-group has-feedback">
<i ref="urlspinner" className="fa fa-spinner fa-spin form-control-feedback" /> <div className="input-group">
</div> <span className="input-group-addon" id="dburladdon">
<div className="input-group spacing"> Database URL
<span className="input-group-addon" id="dbuseraddon">DB Username</span> </span>
<input ref="user" type="text" value={this.state.user} onKeyUp={this._triggerLogin.bind(this)} onChange={this._userChanged.bind(this)} className="form-control" placeholder="neo4j" aria-describedby="dbuseraddon" /> <input ref="url" onFocus={function(){jQuery(this.state.icon).tooltip('hide');}.bind(this)} onBlur={this.checkDBPresence.bind(this)} onChange={this._urlChanged.bind(this)} type="text" className="form-control" value={this.state.url} placeholder="bolt://localhost:7687" aria-describedby="dburladdon" />
</div> <i ref="urlspinner" className="fa fa-spinner fa-spin form-control-feedback" />
<div className="input-group spacing"> </div>
<span className="input-group-addon" id="dbpwaddon">DB Password</span> <div className="input-group spacing">
<input ref="password" value={this.state.password} onKeyDown={this._triggerLogin.bind(this)} onChange={this._passChanged.bind(this)} type="password" className="form-control" placeholder="neo4j" aria-describedby="dbpwaddon" /> <span className="input-group-addon" id="dbuseraddon">DB Username</span>
</div> <input ref="user" type="text" value={this.state.user} onKeyUp={this._triggerLogin.bind(this)} onChange={this._userChanged.bind(this)} className="form-control" placeholder="neo4j" aria-describedby="dbuseraddon" />
<div className="savecontainer"> </div>
<div className="checkbox logincheck"> <div className="input-group spacing">
<label><input value={this.state.save} onChange={this._saveChange.bind(this)} ref="save" type="checkbox" />Save Password</label> <span className="input-group-addon" id="dbpwaddon">DB Password</span>
</div> <input ref="password" value={this.state.password} onKeyDown={this._triggerLogin.bind(this)} onChange={this._passChanged.bind(this)} type="password" className="form-control" placeholder="neo4j" aria-describedby="dbpwaddon" />
<div className="buttoncontainer"> </div>
<button ref="loginButton" disabled={!this.state.loginEnabled} type="button" onClick={this.checkDBCreds.bind(this)} className="btn btn-primary loginbutton has-spinner"> <div className="savecontainer">
Login <div className="checkbox logincheck">
<span className="button-spinner"> <label><input checked={this.state.save} onChange={this._saveChange.bind(this)} ref="save" type="checkbox" />Save Password</label>
<i className="fa fa-spinner fa-spin" /> </div>
</span> <div className="buttoncontainer">
</button> <button ref="loginButton" disabled={!this.state.loginEnabled} type="button" onClick={this.checkDBCreds.bind(this)} className="btn btn-primary loginbutton has-spinner">
</div> Login
</div> <span className="button-spinner">
</div> <i className="fa fa-spinner fa-spin" />
</form> </span>
</div> </button>
</div> </div>
); </div>
} </div>
</form>
</div>
</div>
);
}
} }

View File

@ -1,138 +1,139 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
export default class Settings extends Component { export default class Settings extends Component {
constructor(){ constructor(){
super(); super();
} }
componentDidMount() { componentDidMount() {
emitter.on('openSettings', function(){ emitter.on('openSettings', function(){
this.openSettings() this.openSettings()
}.bind(this)) }.bind(this))
$(this.refs.edge).simpleSlider({ $(this.refs.edge).simpleSlider({
range: [0,20], range: [0,20],
step: 1, step: 1,
theme: 'volume slideinline' theme: 'volume slideinline'
}) })
$(this.refs.sibling).simpleSlider({ $(this.refs.sibling).simpleSlider({
range: [0,20], range: [0,20],
step: 1, step: 1,
theme: 'volume slideinline' theme: 'volume slideinline'
}) })
$(this.refs.edge).bind('slider:changed', this.edgeChange.bind(this)) $(this.refs.edge).bind('slider:changed', this.edgeChange.bind(this))
$(this.refs.sibling).bind('slider:changed', this.siblingChange.bind(this)) $(this.refs.sibling).bind('slider:changed', this.siblingChange.bind(this))
$(this.refs.edge).simpleSlider('setValue', appStore.performance.edge) $(this.refs.edge).simpleSlider('setValue', appStore.performance.edge)
$(this.refs.sibling).simpleSlider('setValue', appStore.performance.sibling) $(this.refs.sibling).simpleSlider('setValue', appStore.performance.sibling)
$(this.refs.check).prop('checked', appStore.performance.lowGraphics) $(this.refs.check).prop('checked', appStore.performance.lowGraphics)
$(this.refs.debug).prop('checked', appStore.performance.debug) $(this.refs.debug).prop('checked', appStore.performance.debug)
$(this.refs.outer).fadeToggle(0) $(this.refs.outer).fadeToggle(0)
$(this.refs.outer).draggable() $(this.refs.outer).draggable()
} }
edgeChange(event, data){ edgeChange(event, data){
appStore.performance.edge = data.value; appStore.performance.edge = data.value;
$(this.refs.edgeinput).val(data.value) $(this.refs.edgeinput).val(data.value)
conf.set('performance', appStore.performance) conf.set('performance', appStore.performance)
} }
siblingChange(event, data){ siblingChange(event, data){
appStore.performance.sibling = data.value; appStore.performance.sibling = data.value;
$(this.refs.siblinginput).val(data.value) $(this.refs.siblinginput).val(data.value)
conf.set('performance', appStore.performance) conf.set('performance', appStore.performance)
} }
onGfxChange(event){ onGfxChange(event){
$(this.refs.check).prop('checked', event.target.checked) $(this.refs.check).prop('checked', event.target.checked)
appStore.performance.lowGraphics = event.target.checked appStore.performance.lowGraphics = event.target.checked
conf.set('performance', appStore.performance) conf.set('performance', appStore.performance)
emitter.emit('changeGraphicsMode') emitter.emit('changeGraphicsMode')
} }
onDebugChange(event){ onDebugChange(event){
$(this.refs.debug).prop('checked', event.target.checked) $(this.refs.debug).prop('checked', event.target.checked)
appStore.performance.debug = event.target.checked appStore.performance.debug = event.target.checked
conf.set('performance', appStore.performance) conf.set('performance', appStore.performance)
} }
closeSettings(){ closeSettings(){
$(this.refs.outer).fadeToggle(false) $(this.refs.outer).fadeToggle(false)
} }
openSettings(){ openSettings(){
$(this.refs.outer).fadeToggle(false) $(this.refs.outer).fadeToggle(false)
} }
updateSibling(event){ updateSibling(event){
$(this.refs.sibling).simpleSlider('setValue', event.target.value) $(this.refs.sibling).simpleSlider('setValue', event.target.value)
} }
updateEdge(event){ updateEdge(event){
$(this.refs.edge).simpleSlider('setValue', event.target.value) $(this.refs.edge).simpleSlider('setValue', event.target.value)
} }
render() { render() {
return ( return (
<div ref="outer" className="settingsDiv panel panel-default"> <div ref="outer" className="settingsDiv panel panel-default">
<div className="panel-heading"> <div className="panel-heading">
Settings Settings
<button type="button" className="close" onClick={this.closeSettings.bind(this)} aria-label="Close"> <button type="button" className="close" onClick={this.closeSettings.bind(this)} aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div className="panel-body sliderfix"> <div className="panel-body sliderfix">
<div> <div>
<strong>Sibling Collapse Threshold</strong> <strong>Sibling Collapse Threshold</strong>
<i data-toggle="tooltip" <i data-toggle="tooltip"
data-placement="right" data-placement="right"
title="Merge nodes that have the same parent. 0 to Disable, Default 10" title="Merge nodes that have the same parent. 0 to Disable, Default 10"
className="glyphicon glyphicon-question-sign"></i> className="glyphicon glyphicon-question-sign"></i>
<br/> <br/>
<input type="text" ref="sibling" /> <input type="text" ref="sibling" />
<span> <span>
<input onChange={this.updateSibling.bind(this)} type="number" min="0" max="20" className="sliderinput" ref="siblinginput" /> <input onChange={this.updateSibling.bind(this)} type="number" min="0" max="20" className="sliderinput" ref="siblinginput" />
</span> </span>
</div> </div>
<div> <div>
<strong>Node Collapse Threshold</strong> <strong>Node Collapse Threshold</strong>
<i data-toggle="tooltip" <i data-toggle="tooltip"
data-placement="right" data-placement="right"
title="Collapse nodes at the end of paths that only have one relationship. 0 to Disable, Default 5" title="Collapse nodes at the end of paths that only have one relationship. 0 to Disable, Default 5"
className="glyphicon glyphicon-question-sign"></i> className="glyphicon glyphicon-question-sign"></i>
<br /> <br />
<input type="text" ref="edge" /> <input type="text" ref="edge" />
<span> <span>
<input type="number" min="0" max="20" className="sliderinput" ref="edgeinput" /> <input type="number" min="0" max="20" className="sliderinput" ref="edgeinput" />
</span> </span>
</div> </div>
<div className="checkbox-inline"> <br />
<label> <div className="checkbox-inline">
<input ref="debug" type="checkbox" onChange={this.onDebugChange.bind(this)}/> Query Debug Mode <label>
</label> <input ref="debug" type="checkbox" onChange={this.onDebugChange.bind(this)}/> Query Debug Mode
</div> </label>
<i data-toggle="tooltip" </div>
data-placement="right" <i data-toggle="tooltip"
title="Dump queries run into the Raw Query Box" data-placement="right"
className="glyphicon glyphicon-question-sign"></i> title="Dump queries run into the Raw Query Box"
<br /> className="glyphicon glyphicon-question-sign"></i>
<div className="checkbox-inline"> <br />
<label> <div className="checkbox-inline">
<input ref="check" type="checkbox" onChange={this.onGfxChange.bind(this)}/> Low Detail Mode <label>
</label> <input ref="check" type="checkbox" onChange={this.onGfxChange.bind(this)}/> Low Detail Mode
</div> </label>
<i data-toggle="tooltip" </div>
data-placement="right" <i data-toggle="tooltip"
title="Lower detail of graph to improve performance" data-placement="right"
className="glyphicon glyphicon-question-sign"></i> title="Lower detail of graph to improve performance"
</div> className="glyphicon glyphicon-question-sign"></i>
</div> </div>
); </div>
} );
}
} }

View File

@ -1,15 +1,15 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom';
import { findGraphPath } from 'utils'; import { findGraphPath } from 'utils';
var fs = require('fs'); var fs = require('fs');
var child_process = require('child_process') var child_process = require('child_process');
var child; var child;
var path = require('path') var path = require('path');
const { dialog } = require('electron').remote const { dialog } = require('electron').remote;
export default class GraphContainer extends Component { export default class GraphContainer extends Component {
constructor(props){ constructor(props){
super(props) super(props);
child = child_process.fork(path.join(__dirname,'src','js','worker.js'), {silent:true}); child = child_process.fork(path.join(__dirname,'src','js','worker.js'), {silent:true});
@ -20,14 +20,15 @@ export default class GraphContainer extends Component {
firstDraw: true, firstDraw: true,
template: null, template: null,
session: driver.session() session: driver.session()
} };
$.ajax({ $.ajax({
url: 'src/components/tooltip.html', url: 'src/components/tooltip.html',
type: 'GET', type: 'GET',
success: function(response){ success: function(response){
this.setState({template: response}) this.setState({template: response});
}.bind(this) }.bind(this)
}) });
child.stdout.on('data', (data) => { child.stdout.on('data', (data) => {
console.log(`stdout: ${data}`); console.log(`stdout: ${data}`);
@ -38,64 +39,93 @@ export default class GraphContainer extends Component {
}); });
child.on('message', function(m) { child.on('message', function(m) {
this.loadFromChildProcess(m) this.loadFromChildProcess(m);
}.bind(this)); }.bind(this));
var s1 = driver.session() var s1 = driver.session();
var s2 = driver.session() var s2 = driver.session();
var s3 = driver.session() var s3 = driver.session();
var s4 = driver.session() var s4 = driver.session();
s1.run("CREATE CONSTRAINT ON (c:User) ASSERT c.name IS UNIQUE") s1.run("CREATE CONSTRAINT ON (c:User) ASSERT c.name IS UNIQUE")
.then(function(){ .then(function(){
s1.close() s1.close();
s2.run("CREATE CONSTRAINT ON (c:Computer) ASSERT c.name IS UNIQUE") s2.run("CREATE CONSTRAINT ON (c:Computer) ASSERT c.name IS UNIQUE")
.then(function(){ .then(function(){
s2.close() s2.close();
s3.run("CREATE CONSTRAINT ON (c:Group) ASSERT c.name IS UNIQUE") s3.run("CREATE CONSTRAINT ON (c:Group) ASSERT c.name IS UNIQUE")
.then(function(){ .then(function(){
s3.close() s3.close();
s4.run("CREATE CONSTRAINT ON (c:Domain) ASSERT c.name IS UNIQUE") s4.run("CREATE CONSTRAINT ON (c:Domain) ASSERT c.name IS UNIQUE")
.then(function(){ .then(function(){
s4.close() s4.close();
}) })
.catch(function(){ .catch(function(){
s4.close() s4.close();
}) });
}) })
.catch(function(){ .catch(function(){
s3.close() s3.close();
}) });
}) })
.catch(function(){ .catch(function(){
s2.close() s2.close();
}) });
}) })
.catch(function(){ .catch(function(){
s1.close() s1.close();
}) });
emitter.on('doLogout', function(){ emitter.on('doLogout', function(){
this.state.sigmaInstance.graph.clear(); this.state.sigmaInstance.graph.clear();
this.state.sigmaInstance.refresh(); this.state.sigmaInstance.refresh();
sigma.layouts.killForceLink(); sigma.layouts.killForceLink();
this.setState({sigmaInstance: null}) this.setState({sigmaInstance: null});
child.kill(); child.kill();
}.bind(this)) }.bind(this));
}
componentWillMount() {
emitter.on('searchQuery', this.doSearchQuery.bind(this));
emitter.on('pathQuery', this.doPathQuery.bind(this));
emitter.on('graphBack', this.goBack.bind(this));
emitter.on('query', this.doGenericQuery.bind(this));
emitter.on('spotlightClick', this.spotlightClickHandler.bind(this));
emitter.on('graphRefresh', this.relayout.bind(this));
emitter.on('export', this.export.bind(this));
emitter.on('import', this.import.bind(this));
emitter.on('clearDB', this.clearGraph.bind(this));
emitter.on('changeGraphicsMode', this.setGraphicsMode.bind(this));
emitter.on('ungroupNode', this.ungroupNode.bind(this));
emitter.on('unfoldNode', this.unfoldEdgeNode.bind(this));
emitter.on('collapseNode', this.foldEdgeNode.bind(this));
emitter.on('resetZoom', this.resetZoom.bind(this));
emitter.on('zoomIn', this.zoomIn.bind(this));
emitter.on('zoomOut', this.zoomOut.bind(this));
}
componentDidMount() {
this.initializeSigma();
this.doQueryNative({
statement: 'MATCH (n:Group) WHERE n.name =~ "(?i).*DOMAIN ADMINS.*" WITH n MATCH (n)<-[r:MemberOf*1..]-(m) RETURN n,r,m',
allowCollapse: false,
props: {}
});
} }
relayout(){ relayout(){
sigma.layouts.stopForceLink() sigma.layouts.stopForceLink();
if (appStore.dagre){ if (appStore.dagre){
sigma.layouts.dagre.start(this.state.sigmaInstance); sigma.layouts.dagre.start(this.state.sigmaInstance);
}else{ }else{
sigma.layouts.startForceLink() sigma.layouts.startForceLink();
} }
} }
export(payload){ export(payload){
if (payload === 'image'){ if (payload === 'image'){
var size = $('#graph').outerWidth() var size = $('#graph').outerWidth();
sigma.plugins.image(this.state.sigmaInstance, sigma.plugins.image(this.state.sigmaInstance,
this.state.sigmaInstance.renderers[0], this.state.sigmaInstance.renderers[0],
{ {
@ -106,27 +136,27 @@ export default class GraphContainer extends Component {
}); });
}else{ }else{
var json = this.state.sigmaInstance.toJSON({ var json = this.state.sigmaInstance.toJSON({
pretty: true, pretty: true
}) });
json = JSON.parse(json) json = JSON.parse(json);
json.spotlight = appStore.spotlightData json.spotlight = appStore.spotlightData;
dialog.showSaveDialog({ dialog.showSaveDialog({
defaultPath: 'graph.json' defaultPath: 'graph.json'
}, function(loc){ }, function(loc){
fs.writeFile(loc, JSON.stringify(json, null, 2)) fs.writeFile(loc, JSON.stringify(json, null, 2));
}) });
} }
} }
loadFromChildProcess(graph){ loadFromChildProcess(graph){
if (graph.nodes.length === 0){ if (graph.nodes.length === 0){
emitter.emit('showAlert', "No data returned from query") emitter.emit('showAlert', "No data returned from query");
emitter.emit('updateLoadingText', "Done!") emitter.emit('updateLoadingText', "Done!");
setTimeout(function(){ setTimeout(function(){
emitter.emit('showLoadingIndicator', false); emitter.emit('showLoadingIndicator', false);
}, 1500) }, 1500);
}else{ }else{
if (!this.state.firstDraw){ if (!this.state.firstDraw){
appStore.queryStack.push({ appStore.queryStack.push({
@ -135,23 +165,23 @@ export default class GraphContainer extends Component {
spotlight: appStore.spotlightData, spotlight: appStore.spotlightData,
startNode: appStore.startNode, startNode: appStore.startNode,
endNode: appStore.endNode endNode: appStore.endNode
}) });
} }
$.each(graph.nodes, function(i, node){ $.each(graph.nodes, function(i, node){
if (node.start){ if (node.start){
appStore.startNode = node appStore.startNode = node;
} }
if (node.end){ if (node.end){
appStore.endNode = node appStore.endNode = node;
} }
node.glyphs = $.map(node.glyphs, function(value, index) { node.glyphs = $.map(node.glyphs, function(value, index) {
return [value]; return [value];
}); });
}) });
this.setState({firstDraw: false}) this.setState({firstDraw: false});
sigma.misc.animation.camera(this.state.sigmaInstance.camera, { x: 0, y: 0, ratio: 1.075 }); sigma.misc.animation.camera(this.state.sigmaInstance.camera, { x: 0, y: 0, ratio: 1.075 });
appStore.spotlightData = graph.spotlight; appStore.spotlightData = graph.spotlight;
@ -164,7 +194,7 @@ export default class GraphContainer extends Component {
if (appStore.dagre){ if (appStore.dagre){
sigma.layouts.dagre.start(this.state.sigmaInstance); sigma.layouts.dagre.start(this.state.sigmaInstance);
}else{ }else{
sigma.layouts.startForceLink() sigma.layouts.startForceLink();
} }
emitter.emit('spotlightUpdate'); emitter.emit('spotlightUpdate');
} }
@ -177,44 +207,44 @@ export default class GraphContainer extends Component {
graph = JSON.parse(data); graph = JSON.parse(data);
}catch (err){ }catch (err){
emitter.emit('showAlert', 'Bad JSON File'); emitter.emit('showAlert', 'Bad JSON File');
return return;
} }
if (graph.nodes.length === 0){ if (graph.nodes.length === 0){
emitter.emit('showAlert', "No data returned from query") emitter.emit('showAlert', "No data returned from query");
}else{ }else{
$.each(graph.nodes, function(i, node){ $.each(graph.nodes, function(i, node){
node.glyphs = $.map(node.glyphs, function(value, index) { node.glyphs = $.map(node.glyphs, function(value, index) {
return [value]; return [value];
}); });
}) });
appStore.queryStack.push({ appStore.queryStack.push({
nodes: this.state.sigmaInstance.graph.nodes(), nodes: this.state.sigmaInstance.graph.nodes(),
edges: this.state.sigmaInstance.graph.edges(), edges: this.state.sigmaInstance.graph.edges(),
spotlight: appStore.spotlightData, spotlight: appStore.spotlightData,
startNode: appStore.startNode, startNode: appStore.startNode,
endNode: appStore.endNode endNode: appStore.endNode
}) });
appStore.spotlightData = graph.spotlight; appStore.spotlightData = graph.spotlight;
this.state.sigmaInstance.graph.clear(); this.state.sigmaInstance.graph.clear();
this.state.sigmaInstance.graph.read(graph); this.state.sigmaInstance.graph.read(graph);
this.state.sigmaInstance.refresh() this.state.sigmaInstance.refresh();
emitter.emit('spotlightUpdate'); emitter.emit('spotlightUpdate');
} }
}.bind(this)) }.bind(this));
} }
clearGraph(){ clearGraph(){
this.state.sigmaInstance.graph.clear() this.state.sigmaInstance.graph.clear();
this.state.sigmaInstance.refresh() this.state.sigmaInstance.refresh();
} }
setGraphicsMode(){ setGraphicsMode(){
var lowgfx = appStore.performance.lowGraphics var lowgfx = appStore.performance.lowGraphics;
var sigmaInstance = this.state.sigmaInstance var sigmaInstance = this.state.sigmaInstance;
this.state.design.clear() this.state.design.clear();
if (lowgfx){ if (lowgfx){
sigmaInstance.settings('defaultEdgeType', 'line'); sigmaInstance.settings('defaultEdgeType', 'line');
sigmaInstance.settings('defaultEdgeColor', 'black'); sigmaInstance.settings('defaultEdgeColor', 'black');
@ -226,28 +256,9 @@ export default class GraphContainer extends Component {
this.state.design.setPalette(appStore.highResPalette); this.state.design.setPalette(appStore.highResPalette);
this.state.design.setStyles(appStore.highResStyle); this.state.design.setStyles(appStore.highResStyle);
} }
this.state.design.deprecate() this.state.design.deprecate();
sigmaInstance.refresh() sigmaInstance.refresh();
this.state.design.apply() this.state.design.apply();
}
componentWillMount() {
emitter.on('searchQuery', this.doSearchQuery.bind(this));
emitter.on('pathQuery', this.doPathQuery.bind(this));
emitter.on('graphBack', this.goBack.bind(this));
emitter.on('query', this.doGenericQuery.bind(this));
emitter.on('spotlightClick', this.spotlightClickHandler.bind(this))
emitter.on('graphRefresh', this.relayout.bind(this))
emitter.on('export', this.export.bind(this))
emitter.on('import', this.import.bind(this))
emitter.on('clearDB', this.clearGraph.bind(this))
emitter.on('changeGraphicsMode', this.setGraphicsMode.bind(this))
emitter.on('ungroupNode', this.ungroupNode.bind(this))
emitter.on('unfoldNode', this.unfoldEdgeNode.bind(this))
emitter.on('collapseNode', this.foldEdgeNode.bind(this))
emitter.on('resetZoom', this.resetZoom.bind(this))
emitter.on('zoomIn', this.zoomIn.bind(this))
emitter.on('zoomOut', this.zoomOut.bind(this))
} }
resetZoom(){ resetZoom(){
@ -258,7 +269,7 @@ export default class GraphContainer extends Component {
} }
zoomOut(){ zoomOut(){
var sigmaInstance = this.state.sigmaInstance var sigmaInstance = this.state.sigmaInstance;
var cam = sigmaInstance.camera; var cam = sigmaInstance.camera;
sigma.misc.animation.camera(cam, { sigma.misc.animation.camera(cam, {
@ -269,7 +280,7 @@ export default class GraphContainer extends Component {
} }
zoomIn(){ zoomIn(){
var sigmaInstance = this.state.sigmaInstance var sigmaInstance = this.state.sigmaInstance;
var cam = sigmaInstance.camera; var cam = sigmaInstance.camera;
sigma.misc.animation.camera(cam, sigma.misc.animation.camera(cam,
@ -281,16 +292,6 @@ export default class GraphContainer extends Component {
}); });
} }
componentDidMount() {
this.initializeSigma();
this.doQueryNative({
statement: 'MATCH (n:Group) WHERE n.name =~ "(?i).*DOMAIN ADMINS.*" WITH n MATCH (n)<-[r:MemberOf*1..]-(m) RETURN n,r,m',
allowCollapse: false,
props: {}
})
}
render() { render() {
return ( return (
<div className="graph"> <div className="graph">
@ -321,25 +322,25 @@ export default class GraphContainer extends Component {
spotlightClickHandler(nodeId, parentId){ spotlightClickHandler(nodeId, parentId){
var sigmaInstance = this.state.sigmaInstance; var sigmaInstance = this.state.sigmaInstance;
var parent = sigmaInstance.graph.nodes(nodeId) var parent = sigmaInstance.graph.nodes(nodeId);
var label, child; var label, child;
if (typeof parent === 'undefined'){ if (typeof parent === 'undefined'){
child = sigmaInstance.graph.nodes(parentId).folded.nodes.filter(function(val){ child = sigmaInstance.graph.nodes(parentId).folded.nodes.filter(function(val){
return val.id == nodeId; return val.id === nodeId;
})[0] })[0];
parent = sigmaInstance.graph.nodes(parentId); parent = sigmaInstance.graph.nodes(parentId);
}else{ }else{
child = parent; child = parent;
} }
label = child.label; label = child.label;
if (child.type_user){ if (child.type_user){
emitter.emit('userNodeClicked', label) emitter.emit('userNodeClicked', label);
}else if (child.type_group){ }else if (child.type_group){
emitter.emit('groupNodeClicked', label) emitter.emit('groupNodeClicked', label);
}else if (child.type_computer){ }else if (child.type_computer){
emitter.emit('computerNodeClicked', label) emitter.emit('computerNodeClicked', label);
} }
parent.color = "#2DC486" parent.color = "#2DC486";
sigma.misc.animation.camera( sigma.misc.animation.camera(
sigmaInstance.camera, { sigmaInstance.camera, {
x: parent[sigmaInstance.camera.readPrefix + 'x'], x: parent[sigmaInstance.camera.readPrefix + 'x'],
@ -351,119 +352,120 @@ export default class GraphContainer extends Component {
setTimeout(function(){ setTimeout(function(){
parent.color = "black"; parent.color = "black";
sigmaInstance.refresh({skipIndexation: true}); sigmaInstance.refresh({skipIndexation: true});
}, 2000) }, 2000);
} }
doQueryNative(params){ doQueryNative(params){
if (appStore.performance.debug){ if (appStore.performance.debug){
emitter.emit('setRawQuery',params.statement); emitter.emit('setRawQuery',params.statement);
} }
var sigmaInstance = this.state.sigmaInstance var sigmaInstance = this.state.sigmaInstance;
var nodes = {} var nodes = {};
var edges = {} var edges = {};
var session = driver.session() var session = driver.session();
if (typeof params.props === 'undefined'){ if (typeof params.props === 'undefined'){
params.props = {} params.props = {};
} }
emitter.emit('showLoadingIndicator', true); emitter.emit('showLoadingIndicator', true);
emitter.emit('updateLoadingText', "Querying Database") emitter.emit('updateLoadingText', "Querying Database");
emitter.emit('resetSpotlight') emitter.emit('resetSpotlight');
session.run(params.statement, params.props) session.run(params.statement, params.props)
.subscribe({ .subscribe({
onNext: function(result){ onNext: function(result){
$.each(result._fields, function(index, field){ $.each(result._fields, function(index, field){
if (field != null){ if (field !== null){
if (field.hasOwnProperty('segments')){ if (field.hasOwnProperty('segments')){
$.each(field.segments,function(index, segment){ $.each(field.segments,function(index, segment){
var end = this.createNodeFromRow(segment.end, params) var end = this.createNodeFromRow(segment.end, params);
var start = this.createNodeFromRow(segment.start, params) var start = this.createNodeFromRow(segment.start, params);
var edge = this.createEdgeFromRow(segment.relationship) var edge = this.createEdgeFromRow(segment.relationship);
if (!edges[edge.id]){ if (!edges[edge.id]){
edges[edge.id] = edge edges[edge.id] = edge;
} }
if (!nodes[end.id]){ if (!nodes[end.id]){
nodes[end.id] = end nodes[end.id] = end;
} }
if (!nodes[start.id]){ if (!nodes[start.id]){
nodes[start.id] = start nodes[start.id] = start;
} }
}.bind(this)) }.bind(this));
}else{ }else{
if ($.isArray(field)){ if ($.isArray(field)){
$.each(field, function(index, value){ $.each(field, function(index, value){
if (value != null){ if (value !== null){
var id = value.identity.low var id = value.identity.low;
if (value.end && !edges.id){ if (value.end && !edges.id){
edges[id] = this.createEdgeFromRow(value) edges[id] = this.createEdgeFromRow(value);
}else if (!nodes.id){ }else if (!nodes.id){
nodes[id] = this.createNodeFromRow(value, params) nodes[id] = this.createNodeFromRow(value, params);
} }
} }
}.bind(this)) }.bind(this));
}else{ }else{
var id = field.identity.low var id = field.identity.low;
if (field.end && !edges.id){ if (field.end && !edges.id){
edges[id] = this.createEdgeFromRow(field) edges[id] = this.createEdgeFromRow(field);
}else if (!nodes.id){ }else if (!nodes.id){
nodes[id] = this.createNodeFromRow(field, params) nodes[id] = this.createNodeFromRow(field, params);
} }
} }
} }
} }
}.bind(this)) }.bind(this));
}.bind(this), }.bind(this),
onError: function(error){ onError: function(error){
console.log(error) console.log(error);
}, },
onCompleted: function(){ onCompleted: function(){
var graph = {nodes:[],edges:[]} var graph = {nodes:[],edges:[]};
$.each(nodes, function(node){ $.each(nodes, function(node){
graph.nodes.push(nodes[node]) graph.nodes.push(nodes[node]);
}) });
$.each(edges, function(edge){ $.each(edges, function(edge){
graph.edges.push(edges[edge]) graph.edges.push(edges[edge]);
}) });
emitter.emit('updateLoadingText', "Processing Data") emitter.emit('updateLoadingText', "Processing Data");
child.send(JSON.stringify({graph: graph, child.send(JSON.stringify({graph: graph,
edge: params.allowCollapse ? appStore.performance.edge : 0 , edge: params.allowCollapse ? appStore.performance.edge : 0 ,
sibling: params.allowCollapse ? appStore.performance.sibling : 0, sibling: params.allowCollapse ? appStore.performance.sibling : 0,
start: params.start, start: params.start,
end: params.end end: params.end
})) }));
session.close() session.close();
}.bind(this) }.bind(this)
}) });
} }
createEdgeFromRow(data){ createEdgeFromRow(data){
var id = data.identity.low var id = data.identity.low;
var type = data.type var type = data.type;
var source = data.start.low var source = data.start.low;
var target = data.end.low var target = data.end.low;
var edge = { var edge = {
id: id, id: id,
type: type, type: type,
source: source, source: source,
target:target, target:target,
label: type label: type
} };
return edge return edge;
} }
createNodeFromRow(data, params){ createNodeFromRow(data, params){
var id = data.identity.low var id = data.identity.low;
var type = data.labels[0] var type = data.labels[0];
var label = data.properties.name var label = data.properties.name;
var node = { var node = {
id: id, id: id,
type: type, type: type,
label: label, label: label,
Enabled: data.properties.Enabled,
glyphs: [], glyphs: [],
folded: { folded: {
nodes: [], nodes: [],
@ -471,28 +473,28 @@ export default class GraphContainer extends Component {
}, },
x: Math.random(), x: Math.random(),
y: Math.random() y: Math.random()
} };
if (label === params.start){ if (label === params.start){
node.start = true node.start = true;
node.glyphs.push({ node.glyphs.push({
'position': 'bottom-right', 'position': 'bottom-right',
'font': 'FontAwesome', 'font': 'FontAwesome',
'content': '\uF21D', 'content': '\uF21D',
'fillColor': '#3399FF', 'fillColor': '#3399FF',
'fontScale': 1.5 'fontScale': 1.5
}) });
} }
if (label === params.end){ if (label === params.end){
node.end = true node.end = true;
node.glyphs.push({ node.glyphs.push({
'position': 'bottom-right', 'position': 'bottom-right',
'font': 'FontAwesome', 'font': 'FontAwesome',
'fillColor': '#990000', 'fillColor': '#990000',
'content': '\uF05B', 'content': '\uF05B',
'fontScale': 1.5 'fontScale': 1.5
}) });
} }
switch (type) { switch (type) {
@ -510,60 +512,60 @@ export default class GraphContainer extends Component {
break; break;
} }
return node return node;
} }
unfoldEdgeNode(id){ unfoldEdgeNode(id){
var sigmaInstance = this.state.sigmaInstance var sigmaInstance = this.state.sigmaInstance;
sigmaInstance.graph.read(sigmaInstance.graph.nodes(id).folded) sigmaInstance.graph.read(sigmaInstance.graph.nodes(id).folded);
this.state.design.deprecate() this.state.design.deprecate();
this.state.design.apply(); this.state.design.apply();
this.relayout() this.relayout();
} }
foldEdgeNode(id){ foldEdgeNode(id){
var sigmaInstance = this.state.sigmaInstance var sigmaInstance = this.state.sigmaInstance;
$.each(sigmaInstance.graph.nodes(id).folded.nodes, function(index, node){ $.each(sigmaInstance.graph.nodes(id).folded.nodes, function(index, node){
sigmaInstance.graph.dropNode(node.id) sigmaInstance.graph.dropNode(node.id);
}) });
sigmaInstance.refresh() sigmaInstance.refresh();
this.state.design.deprecate(); this.state.design.deprecate();
this.state.design.apply(); this.state.design.apply();
this.relayout(); this.relayout();
} }
ungroupNode(id){ ungroupNode(id){
var sigmaInstance = this.state.sigmaInstance var sigmaInstance = this.state.sigmaInstance;
var node = sigmaInstance.graph.nodes(id) var node = sigmaInstance.graph.nodes(id);
sigmaInstance.graph.dropNode(id); sigmaInstance.graph.dropNode(id);
sigmaInstance.graph.read(node.folded) sigmaInstance.graph.read(node.folded);
this.state.design.deprecate() this.state.design.deprecate();
sigmaInstance.refresh() sigmaInstance.refresh();
this.state.design.apply() this.state.design.apply();
this.relayout(); this.relayout();
} }
doSearchQuery(payload, props){ doSearchQuery(payload, props){
if (typeof props === 'undefined'){ if (typeof props === 'undefined'){
props = {} props = {};
} }
this.doQueryNative({ this.doQueryNative({
statement: payload, statement: payload,
allowCollapse: true, allowCollapse: true,
props: props props: props
}) });
} }
doPathQuery(start, end){ doPathQuery(start, end){
var statement = "MATCH (n {name:{start}}), (m {name:{end}}), p=allShortestPaths((n)-[*]->(m)) RETURN p" var statement = "MATCH (n {name:{start}}), (m {name:{end}}), p=allShortestPaths((n)-[*]->(m)) RETURN p";
var props = {start: start, end: end} var props = {start: start, end: end};
this.doQueryNative({ this.doQueryNative({
statement: statement, statement: statement,
allowCollapse: true, allowCollapse: true,
props: props, props: props,
start: start, start: start,
end: end end: end
}) });
} }
doGenericQuery(statement, props, start, end, allowCollapse=true){ doGenericQuery(statement, props, start, end, allowCollapse=true){
@ -572,7 +574,7 @@ export default class GraphContainer extends Component {
} }
if (typeof props === 'undefined'){ if (typeof props === 'undefined'){
props = {} props = {};
} }
this.doQueryNative({ this.doQueryNative({
statement: statement, statement: statement,
@ -580,26 +582,26 @@ export default class GraphContainer extends Component {
start: start, start: start,
end: end, end: end,
props: props props: props
}) });
} }
_nodeDragged(){ _nodeDragged(){
this.setState({dragged:true}) this.setState({dragged:true});
} }
_nodeClicked(n){ _nodeClicked(n){
if (!this.state.dragged){ if (!this.state.dragged){
if (n.data.node.type_user){ if (n.data.node.type_user){
emitter.emit('userNodeClicked', n.data.node.label) emitter.emit('userNodeClicked', n.data.node.label);
}else if (n.data.node.type_group){ }else if (n.data.node.type_group){
emitter.emit('groupNodeClicked', n.data.node.label) emitter.emit('groupNodeClicked', n.data.node.label);
}else if (n.data.node.type_computer && (n.data.node.label !== 'Grouped Computers')){ }else if (n.data.node.type_computer && (n.data.node.label !== 'Grouped Computers')){
emitter.emit('computerNodeClicked', n.data.node.label) emitter.emit('computerNodeClicked', n.data.node.label);
}else if (n.data.node.type_domain){ }else if (n.data.node.type_domain){
emitter.emit('domainNodeClicked', n.data.node.label) emitter.emit('domainNodeClicked', n.data.node.label);
} }
}else{ }else{
this.setState({dragged: false}) this.setState({dragged: false});
} }
} }
@ -610,7 +612,7 @@ export default class GraphContainer extends Component {
{ {
container: 'graph' container: 'graph'
} }
) );
sigmaInstance.settings( sigmaInstance.settings(
{ {
@ -629,7 +631,7 @@ export default class GraphContainer extends Component {
zoomingRatio: 1.4, zoomingRatio: 1.4,
scalingMode: 'inside' scalingMode: 'inside'
} }
) );
//Bind sigma events //Bind sigma events
sigmaInstance.renderers[0].bind('render', function(e) { sigmaInstance.renderers[0].bind('render', function(e) {
@ -642,21 +644,21 @@ export default class GraphContainer extends Component {
}else{ }else{
sigmaInstance.settings('drawEdgeLabels', true); sigmaInstance.settings('drawEdgeLabels', true);
} }
}) });
sigmaInstance.bind('clickNode', this._nodeClicked.bind(this)) sigmaInstance.bind('clickNode', this._nodeClicked.bind(this));
sigmaInstance.bind('hovers', function(e){ sigmaInstance.bind('hovers', function(e){
if (e.data.enter.nodes.length > 0) { if (e.data.enter.nodes.length > 0) {
if (appStore.endNode !== null) { if (appStore.endNode !== null) {
findGraphPath(this.state.sigmaInstance, false, e.data.enter.nodes[0].id) findGraphPath(this.state.sigmaInstance, false, e.data.enter.nodes[0].id);
} }
if (appStore.startNode !== null) { if (appStore.startNode !== null) {
findGraphPath(this.state.sigmaInstance, true, e.data.enter.nodes[0].id) findGraphPath(this.state.sigmaInstance, true, e.data.enter.nodes[0].id);
} }
sigmaInstance.refresh({'skipIndexation': true}) sigmaInstance.refresh({'skipIndexation': true});
} }
if (e.data.leave.nodes.length > 0) { if (e.data.leave.nodes.length > 0) {
@ -668,13 +670,13 @@ export default class GraphContainer extends Component {
sigmaInstance.refresh({ 'skipIndexation': true }); sigmaInstance.refresh({ 'skipIndexation': true });
} }
} }
}.bind(this)) }.bind(this));
//Some key binds //Some key binds
$(window).on('keyup', function(e){ $(window).on('keyup', function(e){
var key = e.keyCode ? e.keyCode : e.which var key = e.keyCode ? e.keyCode : e.which;
var mode = appStore.performance.nodeLabels var mode = appStore.performance.nodeLabels;
var sigmaInstance = this.state.sigmaInstance var sigmaInstance = this.state.sigmaInstance;
if (document.activeElement === document.body && key === 17){ if (document.activeElement === document.body && key === 17){
mode = mode + 1; mode = mode + 1;
@ -682,28 +684,28 @@ export default class GraphContainer extends Component {
mode = 0; mode = 0;
} }
appStore.performance.nodeLabels = mode; appStore.performance.nodeLabels = mode;
conf.set('performance', appStore.performance) conf.set('performance', appStore.performance);
if (mode === 0){ if (mode === 0){
sigmaInstance.settings('labelThreshold', 500); sigmaInstance.settings('labelThreshold', 500);
emitter.emit('showAlert', 'Hiding Node Labels') emitter.emit('showAlert', 'Hiding Node Labels');
}else if (mode === 1){ }else if (mode === 1){
sigmaInstance.settings('labelThreshold', 15); sigmaInstance.settings('labelThreshold', 15);
emitter.emit('showAlert', 'Default Node Label Threshold') emitter.emit('showAlert', 'Default Node Label Threshold');
}else{ }else{
sigmaInstance.settings('labelThreshold', 1); sigmaInstance.settings('labelThreshold', 1);
emitter.emit('showAlert', 'Always Showing Node Labels') emitter.emit('showAlert', 'Always Showing Node Labels');
} }
sigmaInstance.refresh({'skipIndexation' : true}) sigmaInstance.refresh({'skipIndexation' : true});
} }
}.bind(this)) }.bind(this));
//Plugin Configuration //Plugin Configuration
var dragListener = sigma.plugins.dragNodes(sigmaInstance, var dragListener = sigma.plugins.dragNodes(sigmaInstance,
sigmaInstance.renderers[0]) sigmaInstance.renderers[0]);
dragListener.bind('drag', this._nodeDragged.bind(this)) dragListener.bind('drag', this._nodeDragged.bind(this));
var tooltips = sigma.plugins.tooltips( var tooltips = sigma.plugins.tooltips(
sigmaInstance, sigmaInstance,
@ -718,7 +720,7 @@ export default class GraphContainer extends Component {
node.expand = false; node.expand = false;
node.collapse = false; node.collapse = false;
if (node.folded.nodes.length > 0 && !node.groupedNode) { if (node.folded.nodes.length > 0 && !node.groupedNode) {
if (typeof this.state.sigmaInstance.graph.nodes(node.folded.nodes[0].id) == 'undefined') { if (typeof this.state.sigmaInstance.graph.nodes(node.folded.nodes[0].id) === 'undefined') {
node.expand = true; node.expand = true;
} else { } else {
node.collapse = true; node.collapse = true;
@ -753,12 +755,12 @@ export default class GraphContainer extends Component {
forcelinkListener.bind('stop', function(event) { forcelinkListener.bind('stop', function(event) {
emitter.emit('updateLoadingText', "Fixing Overlap"); emitter.emit('updateLoadingText', "Fixing Overlap");
sigmaInstance.startNoverlap(); sigmaInstance.startNoverlap();
}) });
forcelinkListener.bind('start', function(event){ forcelinkListener.bind('start', function(event){
emitter.emit('updateLoadingText', 'Initial Layout') emitter.emit('updateLoadingText', 'Initial Layout');
emitter.emit('showLoadingIndicator', true) emitter.emit('showLoadingIndicator', true);
}) });
var dagreListener = sigma.layouts.dagre.configure(sigmaInstance, { var dagreListener = sigma.layouts.dagre.configure(sigmaInstance, {
easing: 'cubicInOut', easing: 'cubicInOut',
@ -774,22 +776,22 @@ export default class GraphContainer extends Component {
emitter.emit('updateLoadingText', "Fixing Overlap"); emitter.emit('updateLoadingText', "Fixing Overlap");
sigmaInstance.startNoverlap(); sigmaInstance.startNoverlap();
needsfix = true; needsfix = true;
return return;
} }
}, this); }, this);
if (!needsfix){ if (!needsfix){
emitter.emit('updateLoadingText', 'Done!'); emitter.emit('updateLoadingText', 'Done!');
sigma.canvas.edges.autoCurve(sigmaInstance) sigma.canvas.edges.autoCurve(sigmaInstance);
setTimeout(function(){ setTimeout(function(){
emitter.emit('showLoadingIndicator', false); emitter.emit('showLoadingIndicator', false);
}, 1500) }, 1500);
} }
}) });
dagreListener.bind('start', function(event){ dagreListener.bind('start', function(event){
emitter.emit('updateLoadingText', 'Initial Layout') emitter.emit('updateLoadingText', 'Initial Layout');
emitter.emit('showLoadingIndicator', true) emitter.emit('showLoadingIndicator', true);
}) });
// var noverlapListener = sigmaInstance.configNoverlap({ // var noverlapListener = sigmaInstance.configNoverlap({
// nodeMargin: 5.0, // nodeMargin: 5.0,
@ -799,19 +801,19 @@ export default class GraphContainer extends Component {
// }); // });
// //
var noverlapListener = sigmaInstance.configNoverlap({}) var noverlapListener = sigmaInstance.configNoverlap({});
noverlapListener.bind('stop', function(event) { noverlapListener.bind('stop', function(event) {
emitter.emit('updateLoadingText', 'Done!'); emitter.emit('updateLoadingText', 'Done!');
sigma.canvas.edges.autoCurve(sigmaInstance) sigma.canvas.edges.autoCurve(sigmaInstance);
setTimeout(function(){ setTimeout(function(){
emitter.emit('showLoadingIndicator', false); emitter.emit('showLoadingIndicator', false);
}, 1500) }, 1500);
}); });
var lowgfx = appStore.performance.lowGraphics var lowgfx = appStore.performance.lowGraphics;
design = sigma.plugins.design(sigmaInstance); design = sigma.plugins.design(sigmaInstance);
if (lowgfx){ if (lowgfx){
@ -826,7 +828,7 @@ export default class GraphContainer extends Component {
design.setStyles(appStore.highResStyle); design.setStyles(appStore.highResStyle);
} }
var mode = appStore.performance.nodeLabels var mode = appStore.performance.nodeLabels;
if (mode === 0){ if (mode === 0){
sigmaInstance.settings('labelThreshold', 500); sigmaInstance.settings('labelThreshold', 500);

View File

@ -1,284 +1,310 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import MenuButton from './MenuButton'; import MenuButton from './MenuButton';
import ProgressBarMenuButton from './ProgressBarMenuButton'; import ProgressBarMenuButton from './ProgressBarMenuButton';
import { buildDomainProps, buildSessionProps, buildLocalAdminProps, buildGroupMembershipProps, buildACLProps } from 'utils'; import { buildDomainProps, buildSessionProps, buildLocalAdminProps, buildGroupMembershipProps, buildACLProps, findObjectType} from 'utils';
import { If, Then, Else } from 'react-if'; import { If, Then, Else } from 'react-if';
const { dialog, clipboard } = require('electron').remote const { dialog, clipboard, app } = require('electron').remote;
var fs = require('fs') var fs = require('fs');
var async = require('async') var async = require('async');
var unzip = require('unzipper');
var fpath = require('path');
var csv = require('fast-csv');
export default class MenuContainer extends Component { export default class MenuContainer extends Component {
constructor(){ constructor(){
super() super();
this.state = { this.state = {
refreshHover: false, refreshHover: false,
uploading: false, uploading: false,
progress: 0, progress: 0,
parser: null cancelled: false
} };
emitter.on('cancelUpload', this.cancelUpload.bind(this)) emitter.on('cancelUpload', this.cancelUpload.bind(this))
} }
cancelUpload(){ cancelUpload(){
this.state.parser.abort() this.setState({cancelled: true});
setTimeout(function(){ setTimeout(function(){
this.setState({uploading: false}) this.setState({uploading: false})
}.bind(this), 1000) }.bind(this), 1000);
} }
_refreshClick(){ _refreshClick(){
emitter.emit('graphRefresh') emitter.emit('graphRefresh')
} }
_changeLayoutClick(){ _changeLayoutClick(){
appStore.dagre = !appStore.dagre appStore.dagre = !appStore.dagre
emitter.emit('graphRefresh') emitter.emit('graphRefresh')
var type = appStore.dagre ? 'Hierarchical' : 'Directed' var type = appStore.dagre ? 'Hierarchical' : 'Directed'
emitter.emit('showAlert', 'Changed Layout to ' + type) emitter.emit('showAlert', 'Changed Layout to ' + type)
} }
_exportClick(){ _exportClick(){
emitter.emit('showExport'); emitter.emit('showExport');
} }
_importClick(){ _importClick(){
var fname = dialog.showOpenDialog({ var fname = dialog.showOpenDialog({
properties: ['openFile'] properties: ['openFile']
}) });
if (typeof fname !== 'undefined'){ if (typeof fname !== 'undefined'){
emitter.emit('import',fname[0]) emitter.emit('import',fname[0]);
} }
} }
_settingsClick(){ _settingsClick(){
emitter.emit('openSettings') emitter.emit('openSettings')
} }
_cancelUploadClick(){ _cancelUploadClick(){
emitter.emit('showCancelUpload') emitter.emit('showCancelUpload')
} }
_uploadClick(){ _uploadClick(){
var input = jQuery(this.refs.fileInput) var input = jQuery(this.refs.fileInput);
var files = $.makeArray(input[0].files) var fileNames = [];
async.eachSeries(files, function(file, callback){ $.each(input[0].files, function(index, file){
emitter.emit('showAlert', 'Processing file {}'.format(file.name)); fileNames.push({path:file.path, name:file.name});
this.processFile(file.path, file, callback) });
}.bind(this),
function done(){
setTimeout(function(){
this.setState({uploading: false})
}.bind(this), 3000)
}.bind(this))
input.val('') this.unzipNecessary(fileNames).then(function(results){
} async.eachSeries(results, function(file, callback){
emitter.emit('showAlert', 'Processing file {}'.format(file.name));
this.processFile(file.path, callback);
}.bind(this),
function done(){
setTimeout(function(){
this.setState({uploading: false});
}.bind(this), 3000);
$.each(results, function(index, file){
if (file.delete){
fs.unlinkSync(file.path);
}
});
}.bind(this));
input.val('');
}.bind(this));
}
_aboutClick(){ async unzipNecessary(files){
emitter.emit('showAbout') var index = 0;
} var processed = [];
var tempPath = app.getPath('temp');
while (index < files.length){
var path = files[index].path;
var name = files[index].name;
processFile(filename, fileobject, callback){ if (path.endsWith(".zip")){
var sent = 0 await fs.createReadStream(path)
.pipe(unzip.Parse())
.on('entry', function(entry){
var output = fpath.join(tempPath, entry.path);
entry.pipe(fs.createWriteStream(output));
processed.push({path:output, name:entry.path, delete: true});
}).promise();
}else{
processed.push({path:path,name:name, delete: false});
}
index++;
}
var i; return processed;
var count = 0; }
var header = ""
var procHeader = true;
fs.createReadStream(filename)
.on('data', function(chunk) {
for (i=0; i < chunk.length; ++i){
if (procHeader){
header = header + String.fromCharCode(chunk[i])
}
if (chunk[i] == 10){
if (procHeader){
procHeader = false;
}
count++
};
}
})
.on('end', function() {
count = count - 1
var filetype;
if (header.includes('UserName') && header.includes('ComputerName') && header.includes('Weight')){
filetype = 'sessions'
}else if (header.includes('AccountName') && header.includes('AccountType') && header.includes('GroupName')){
filetype = 'groupmembership'
}else if (header.includes('AccountName') && header.includes('AccountType') && header.includes('ComputerName')){
filetype = 'localadmin'
}else if (header.includes('SourceDomain') && header.includes('TargetDomain') && header.includes('TrustDirection') && header.includes('TrustType') && header.includes('Transitive')){
filetype = 'domain'
}else if (header.includes('ActiveDirectoryRights') && header.includes('ObjectType') && header.includes('PrincipalType')){
filetype = 'acl'
}
if (typeof filetype === 'undefined'){ _aboutClick(){
emitter.emit('showAlert', 'Unrecognized CSV Type'); emitter.emit('showAbout');
return; }
}
processFile(file, callback){
console.log(file);
var count = 0;
var header = "";
var processHeader = true;
var fileType;
//Lets calculate the number of lines in the file and get the header
var input = fs.createReadStream(file);
input.on('data', function (chunk){
for (var i=0; i < chunk.length; ++i){
if (processHeader){
header = header + String.fromCharCode(chunk[i]);
}
if (chunk[i] === 10){
//At the first newline, we look at the header to figure out
if (processHeader){
processHeader = false;
fileType = findObjectType(header);
if (fileType === 'unknown'){
emitter.emit('showAlert', 'Unrecognized CSV Type');
input.close();
}
}
count++;
}
}
})
.on('end', function(){
//We've got our line count for progress
var chunk = [];
var localCount = 0;
var sent = 0;
this.setState({ //Subtract one line to account for the header
uploading: true, count--;
progress: 0
}) //Change the UI to display our uploading state
//I have no idea why this workaround is needed. Apparently all my sessions freeze unless I make a random query this.setState({
setTimeout(function(){ uploading: true,
var sess = driver.session() progress: 0
sess.run('MATCH (n) RETURN (n) LIMIT 1') });
.then(function(){
sess.close()
})
}, 1000)
console.time('IngestTime') //Start a timer
Papa.parse(fileobject,{ console.time('IngestTime');
header: true,
dynamicTyping: true,
skipEmptyLines: true,
chunkSize: 5242880,
//chunkSize: 500000,
chunk: function(rows, parser){
this.setState({parser: parser})
if (rows.data.length === 0){
console.timeEnd('IngestTime')
parser.abort()
this.setState({progress:100})
emitter.emit('refreshDBData')
callback()
return
}
parser.pause()
sent += rows.data.length
if (filetype === 'sessions'){
var query = 'UNWIND {props} AS prop MERGE (user:User {name:prop.account}) WITH user,prop MERGE (computer:Computer {name: prop.computer}) WITH user,computer,prop MERGE (computer)-[:HasSession {Weight : prop.weight}]-(user)'
var props = buildSessionProps(rows.data)
var session = driver.session()
session.run(query, {props: props})
.then(function(){
this.setState({progress: Math.floor((sent / count) * 100)})
session.close()
parser.resume()
}.bind(this))
}else if (filetype === 'groupmembership'){
var props = buildGroupMembershipProps(rows.data)
var userQuery = 'UNWIND {props} AS prop MERGE (user:User {name:prop.account}) WITH user,prop MERGE (group:Group {name:prop.group}) WITH user,group MERGE (user)-[:MemberOf]->(group)'
var computerQuery = 'UNWIND {props} AS prop MERGE (computer:Computer {name:prop.account}) WITH computer,prop MERGE (group:Group {name:prop.group}) WITH computer,group MERGE (computer)-[:MemberOf]->(group)'
var groupQuery = 'UNWIND {props} AS prop MERGE (group1:Group {name:prop.account}) WITH group1,prop MERGE (group2:Group {name:prop.group}) WITH group1,group2 MERGE (group1)-[:MemberOf]->(group2)'
var session = driver.session()
var tx = session.beginTransaction()
var promises = []
promises.push(tx.run(userQuery, {props: props.users})) //Start parsing the file
promises.push(tx.run(computerQuery, {props: props.computers})) var parser = csv.fromStream(fs.createReadStream(file),
promises.push(tx.run(groupQuery, {props: props.groups})) {
headers: true,
ignoreEmpty: true
})
.on('data', function(data){
//On each row, push it into an array and increment a counter
chunk.push(data);
localCount++;
Promise.all(promises) //If we've collected 10k rows, push it all to the DB.
.then(function(){ if (localCount % 10000 === 0){
tx.commit() //Pause the parser until upload is complete
.then(function(){ parser.pause();
session.close() this.uploadData(chunk, fileType, count)
this.setState({progress: Math.floor((sent / count) * 100)}) .then(function(){
parser.resume() //Update the sent number, and resume the parser
}.bind(this)) sent += chunk.length;
}.bind(this)) this.setState({progress: Math.floor(sent / count * 100)});
}else if (filetype === 'localadmin'){
var props = buildLocalAdminProps(rows.data)
var userQuery = 'UNWIND {props} AS prop MERGE (user:User {name: prop.account}) WITH user,prop MERGE (computer:Computer {name: prop.computer}) WITH user,computer MERGE (user)-[:AdminTo]->(computer)'
var groupQuery = 'UNWIND {props} AS prop MERGE (group:Group {name: prop.account}) WITH group,prop MERGE (computer:Computer {name: prop.computer}) WITH group,computer MERGE (group)-[:AdminTo]->(computer)'
var computerQuery = 'UNWIND {props} AS prop MERGE (computer1:Computer {name: prop.account}) WITH computer1,prop MERGE (computer2:Computer {name: prop.computer}) WITH computer1,computer2 MERGE (computer1)-[:AdminTo]->(computer2)'
var session = driver.session() chunk = [];
var tx = session.beginTransaction() parser.resume();
var promises = [] }.bind(this));
}
}.bind(this))
.on('end', function(){
//Upload any remaining data
this.uploadData(chunk, fileType, count)
.then(function(){
//Set the uploading state to 100%, refresh the db display, and move on to the next file if there is one
this.setState({progress:100});
emitter.emit('refreshDBData');
console.timeEnd('IngestTime');
callback();
}.bind(this));
}.bind(this));
}.bind(this));
}
async uploadData(currentChunk, filetype, total){
var index = 0;
var processed;
var sent = 0;
var session = driver.session();
promises.push(tx.run(userQuery, {props: props.users})) if (filetype === 'groupmembership'){
promises.push(tx.run(computerQuery, {props: props.computers})) var userQuery = 'UNWIND {props} AS prop MERGE (user:User {name:prop.account}) WITH user,prop MERGE (group:Group {name:prop.group}) WITH user,group MERGE (user)-[:MemberOf {isACL:false}]->(group)';
promises.push(tx.run(groupQuery, {props: props.groups})) var computerQuery = 'UNWIND {props} AS prop MERGE (computer:Computer {name:prop.account}) WITH computer,prop MERGE (group:Group {name:prop.group}) WITH computer,group MERGE (computer)-[:MemberOf {isACL:false}]->(group)';
var groupQuery = 'UNWIND {props} AS prop MERGE (group1:Group {name:prop.account}) WITH group1,prop MERGE (group2:Group {name:prop.group}) WITH group1,group2 MERGE (group1)-[:MemberOf {isACL:false}]->(group2)';
Promise.all(promises) processed = buildGroupMembershipProps(currentChunk);
.then(function(){
tx.commit()
.then(function(){
session.close()
this.setState({progress: Math.floor((sent / count) * 100)})
parser.resume()
}.bind(this))
}.bind(this))
}else if (filetype === 'domain'){
var props = buildDomainProps(rows.data)
var query = "UNWIND {props} AS prop MERGE (domain1:Domain {name: prop.domain1}) WITH domain1,prop MERGE (domain2:Domain {name: prop.domain2}) WITH domain1,domain2,prop MERGE (domain1)-[:TrustedBy {TrustType : prop.trusttype, Transitive: prop.transitive}]->(domain2)"
var session = driver.session()
session.run(query, {props: props})
.then(function(){
this.setState({progress: Math.floor((sent / count) * 100)})
session.close()
parser.resume()
}.bind(this))
}else if (filetype === 'acl'){
var data = buildACLProps(rows.data)
var promises = []
var session = driver.session()
var tx = session.beginTransaction()
for (var key in data){
var promise = tx.run(data[key].statement, {props: data[key].props})
promises.push(promise)
}
Promise.all(promises) await session.run(userQuery, {props: processed.users});
.then(function(){ await session.run(computerQuery, {props: processed.computers});
tx.commit() await session.run(groupQuery, {props: processed.groups});
.then(function(){ }else if (filetype === 'localadmin'){
this.setState({progress: Math.floor((sent / count) * 100)}) userQuery = 'UNWIND {props} AS prop MERGE (user:User {name: prop.account}) WITH user,prop MERGE (computer:Computer {name: prop.computer}) WITH user,computer MERGE (user)-[:AdminTo {isACL:false}]->(computer)';
session.close() groupQuery = 'UNWIND {props} AS prop MERGE (group:Group {name: prop.account}) WITH group,prop MERGE (computer:Computer {name: prop.computer}) WITH group,computer MERGE (group)-[:AdminTo {isACL:false}]->(computer)';
parser.resume() computerQuery = 'UNWIND {props} AS prop MERGE (computer1:Computer {name: prop.account}) WITH computer1,prop MERGE (computer2:Computer {name: prop.computer}) WITH computer1,computer2 MERGE (computer1)-[:AdminTo {isACL:false}]->(computer2)';
}.bind(this))
}.bind(this))
}
}.bind(this)
})
}.bind(this));
}
render() { processed = buildLocalAdminProps(currentChunk);
return (
<div className="menudiv"> await session.run(userQuery, {props: processed.users});
<div> await session.run(computerQuery, {props: processed.computers});
<MenuButton click={this._refreshClick.bind(this)} hoverVal="Refresh" glyphicon="glyphicon glyphicon-refresh" /> await session.run(groupQuery, {props: processed.groups});
</div> }else if (filetype === 'sessions'){
<div> var query = 'UNWIND {props} AS prop MERGE (user:User {name:prop.account}) WITH user,prop MERGE (computer:Computer {name: prop.computer}) WITH user,computer,prop MERGE (computer)-[:HasSession {Weight : prop.weight, isACL: false}]-(user)';
<MenuButton click={this._exportClick.bind(this)} hoverVal="Export Graph" glyphicon="glyphicon glyphicon-export" />
</div> processed = buildSessionProps(currentChunk);
<div>
<MenuButton click={this._importClick.bind(this)} hoverVal="Import Graph" glyphicon="glyphicon glyphicon-import" /> await session.run(query, {props: processed});
</div> }else if (filetype === 'domain'){
<div> query = "UNWIND {props} AS prop MERGE (domain1:Domain {name: prop.domain1}) WITH domain1,prop MERGE (domain2:Domain {name: prop.domain2}) WITH domain1,domain2,prop MERGE (domain1)-[:TrustedBy {TrustType : prop.trusttype, Transitive: toBoolean(prop.transitive), isACL:false}]->(domain2)";
<If condition={this.state.uploading}>
<Then> processed = buildDomainProps(currentChunk);
<ProgressBarMenuButton click={this._cancelUploadClick.bind(this)} progress={this.state.progress} committed={this.state.committed}/>
</Then> await session.run(query, {props: processed});
<Else>{ () => }else if (filetype === 'acl'){
<MenuButton click={function(){jQuery(this.refs.fileInput).click()}.bind(this)} hoverVal="Upload Data" glyphicon="glyphicon glyphicon-upload" /> processed = buildACLProps(currentChunk);
}</Else>
</If> for (var key in processed){
</div> await session.run(processed[key].statement, {props: processed[key].props});
<div> }
<MenuButton click={this._changeLayoutClick.bind(this)} hoverVal="Change Layout Type" glyphicon="fa fa-line-chart" /> }else if (filetype === 'userprops'){
</div> $.each(currentChunk, function(index, obj){
<div> var spn = obj.ServicePrincipalNames;
<MenuButton click={this._settingsClick.bind(this)} hoverVal="Settings" glyphicon="fa fa-cogs" /> var sh = obj.SidHistory;
</div>
<div> if (spn === ""){
<MenuButton click={this._aboutClick.bind(this)} hoverVal="About" glyphicon="fa fa-info" /> obj.ServicePrincipalNames = [];
</div> }else{
<input ref="fileInput" multiple className="hide" type="file" onChange={this._uploadClick.bind(this)}/> obj.ServicePrincipalNames = spn.split('|');
</div> }
); });
} query = 'UNWIND {props} AS prop MERGE (user:User {name: upper(prop.AccountName)}) SET user.Enabled = toBoolean(prop.Enabled),user.PwdLastSet = toInt(prop.PwdLastSet),user.LastLogon = toInt(prop.LastLogon),user.Sid = prop.Sid,user.SidHistory = prop.SidHistory,user.HasSPN = toBoolean(prop.HasSPN),user.DisplayName=prop.DisplayName,user.ServicePrincipalNames = prop.ServicePrincipalNames,user.Email=prop.Email';
await session.run(query, {props:currentChunk});
}else if (filetype === 'compprops'){
query = 'UNWIND {props} AS prop MERGE (comp:Computer {name: upper(prop.AccountName)}) SET comp.Enabled=toBoolean(prop.Enabled),comp.PwdLastSet=toInt(prop.PwdLastSet),comp.LastLogon=toInt(prop.LastLogon),comp.OperatingSystem=prop.OperatingSystem,comp.Sid=prop.Sid,comp.UnconstrainedDelegation=toBoolean(prop.UnconstrainedDelegation)';
await session.run(query, {props:currentChunk});
}
}
render() {
return (
<div className="menudiv">
<div>
<MenuButton click={this._refreshClick.bind(this)} hoverVal="Refresh" glyphicon="glyphicon glyphicon-refresh" />
</div>
<div>
<MenuButton click={this._exportClick.bind(this)} hoverVal="Export Graph" glyphicon="glyphicon glyphicon-export" />
</div>
<div>
<MenuButton click={this._importClick.bind(this)} hoverVal="Import Graph" glyphicon="glyphicon glyphicon-import" />
</div>
<div>
<If condition={this.state.uploading}>
<Then>
<ProgressBarMenuButton click={this._cancelUploadClick.bind(this)} progress={this.state.progress} committed={this.state.committed}/>
</Then>
<Else>{ () =>
<MenuButton click={function(){jQuery(this.refs.fileInput).click()}.bind(this)} hoverVal="Upload Data" glyphicon="glyphicon glyphicon-upload" />
}</Else>
</If>
</div>
<div>
<MenuButton click={this._changeLayoutClick.bind(this)} hoverVal="Change Layout Type" glyphicon="fa fa-line-chart" />
</div>
<div>
<MenuButton click={this._settingsClick.bind(this)} hoverVal="Settings" glyphicon="fa fa-cogs" />
</div>
<div>
<MenuButton click={this._aboutClick.bind(this)} hoverVal="About" glyphicon="fa fa-info" />
</div>
<input ref="fileInput" multiple className="hide" type="file" onChange={this._uploadClick.bind(this)}/>
</div>
);
}
} }

View File

@ -1,80 +1,80 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types' import PropTypes from 'prop-types';
export default class ProgressBarMenuButton extends Component { export default class ProgressBarMenuButton extends Component {
constructor(){ constructor(){
super() super();
this.state = { this.state = {
expanded: false expanded: false
} };
} }
_leave(e){ componentDidMount(){
this.setState({expanded: false}) $(this.refs.btn).html('{}%'.format(this.props.progress));
var target = $(e.target) $(this.refs.btn).css('padding','6px 0px 6px 0px');
var oldWidth = target.width() $(this.refs.btn).css('width','41px');
target.html('{}%'.format(this.props.progress)) }
target.animate({
width: '41px'
}, 100)
}
_enter(e){ componentWillReceiveProps(nextProps){
this.setState({expanded: true}) if (this.state.expanded){
var target = $(e.target) var template = `<div class="progress" style="margin-bottom:0px">
var oldWidth = target.width() <div class="progress-bar progress-bar-striped active" role="progressbar" aria-value-now={} aria-value-max="100" style="width:{}%">
var template = ` </div>
<div class="progress" style="margin-bottom:0px"> <span>
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-value-now={} aria-value-max="100" style="width:{}%"> {}%
</div> </span>
<span> </div>`.formatAll(nextProps.progress);
{}% $(this.refs.btn).html(template);
</span> }else{
</div> $(this.refs.btn).html('{}%'.format(nextProps.progress));
`.formatAll(this.props.progress) }
target.html(template)
target.animate({
width: '150px'
}, 100)
}
componentWillReceiveProps(nextProps){ this.forceUpdate();
if (this.state.expanded){ }
var template = `<div class="progress" style="margin-bottom:0px">
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-value-now={} aria-value-max="100" style="width:{}%">
</div>
<span>
{}%
</span>
</div>`.formatAll(nextProps.progress)
$(this.refs.btn).html(template)
}else{
$(this.refs.btn).html('{}%'.format(nextProps.progress))
}
this.forceUpdate() shouldComponentUpdate(nextProps, nextState){
} return true;
}
shouldComponentUpdate(nextProps, nextState){ _leave(e){
return true this.setState({expanded: false});
} var target = $(e.target);
var oldWidth = target.width();
target.html('{}%'.format(this.props.progress));
target.animate({
width: '41px'
}, 100);
}
componentDidMount(){ _enter(e){
$(this.refs.btn).html('{}%'.format(this.props.progress)) this.setState({expanded: true});
$(this.refs.btn).css('padding','6px 0px 6px 0px') var target = $(e.target);
$(this.refs.btn).css('width','41px') var oldWidth = target.width();
} var template = `
<div class="progress" style="margin-bottom:0px">
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-value-now={} aria-value-max="100" style="width:{}%">
</div>
<span>
{}%
</span>
</div>
`.formatAll(this.props.progress);
target.html(template);
target.animate({
width: '150px'
}, 100);
}
render() { render() {
return ( return (
<button ref="btn" onClick={this.props.click} onMouseLeave={this._leave.bind(this)} onMouseEnter={this._enter.bind(this)} className="btn" /> <button ref="btn" onClick={this.props.click} onMouseLeave={this._leave.bind(this)} onMouseEnter={this._enter.bind(this)} className="btn" />
); );
} }
} }
ProgressBarMenuButton.propTypes = { ProgressBarMenuButton.propTypes = {
progress : React.PropTypes.number.isRequired, progress : React.PropTypes.number.isRequired,
click : React.PropTypes.func.isRequired click : React.PropTypes.func.isRequired
} };

View File

@ -1,86 +1,86 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import GlyphiconSpan from '../GlyphiconSpan' import GlyphiconSpan from '../GlyphiconSpan';
import Icon from '../Icon' import Icon from '../Icon';
import TabContainer from './TabContainer' import TabContainer from './TabContainer';
export default class SearchContainer extends Component { export default class SearchContainer extends Component {
constructor(props){ constructor(props){
super(props) super(props);
this.state = { this.state = {
mainPlaceholder:"Start typing to search for a node...", mainPlaceholder:"Start typing to search for a node...",
pathfindingIsOpen: false, pathfindingIsOpen: false,
mainValue: "", mainValue: "",
pathfindValue: "" pathfindValue: ""
} };
} }
_onPathfindClick(){ _onPathfindClick(){
jQuery(this.refs.pathfinding).slideToggle() jQuery(this.refs.pathfinding).slideToggle();
var p = !this.state.pathfindingIsOpen var p = !this.state.pathfindingIsOpen;
var t = this.state.pathfindingIsOpen ? "Start typing to search for a node..." : "Start Node" var t = this.state.pathfindingIsOpen ? "Start typing to search for a node..." : "Start Node";
this.setState({ this.setState({
pathfindingIsOpen: p, pathfindingIsOpen: p,
mainPlaceholder: t mainPlaceholder: t
}) });
} }
_onPlayClick(){ _onPlayClick(){
var start = jQuery(this.refs.searchbar).val() var start = jQuery(this.refs.searchbar).val();
var end = jQuery(this.refs.pathbar).val() var end = jQuery(this.refs.pathbar).val();
if (start !== "" && end !== ""){ if (start !== "" && end !== ""){
emitter.emit('pathQuery', start, end) emitter.emit('pathQuery', start, end);
} }
} }
_onExpandClick(){ _onExpandClick(){
jQuery(this.refs.tabs).slideToggle() jQuery(this.refs.tabs).slideToggle();
} }
openNodeTab(){ openNodeTab(){
var e = jQuery(this.refs.tabs) var e = jQuery(this.refs.tabs);
if (!(e.is(":visible"))){ if (!(e.is(":visible"))){
e.slideToggle() e.slideToggle();
} }
} }
componentDidMount() { componentDidMount() {
jQuery(this.refs.pathfinding).slideToggle(0); jQuery(this.refs.pathfinding).slideToggle(0);
jQuery(this.refs.tabs).slideToggle(0); jQuery(this.refs.tabs).slideToggle(0);
emitter.on('userNodeClicked', this.openNodeTab.bind(this)) emitter.on('userNodeClicked', this.openNodeTab.bind(this));
emitter.on('groupNodeClicked', this.openNodeTab.bind(this)) emitter.on('groupNodeClicked', this.openNodeTab.bind(this));
emitter.on('computerNodeClicked', this.openNodeTab.bind(this)) emitter.on('computerNodeClicked', this.openNodeTab.bind(this));
emitter.on('domainNodeClicked', this.openNodeTab.bind(this)) emitter.on('domainNodeClicked', this.openNodeTab.bind(this));
emitter.on('setStart', function(payload){ emitter.on('setStart', function(payload){
jQuery(this.refs.searchbar).val(payload); jQuery(this.refs.searchbar).val(payload);
}.bind(this)) }.bind(this));
emitter.on('setEnd', function(payload){ emitter.on('setEnd', function(payload){
jQuery(this.refs.pathbar).val(payload); jQuery(this.refs.pathbar).val(payload);
var e = jQuery(this.refs.pathfinding) var e = jQuery(this.refs.pathfinding);
if (!(e.is(":visible"))){ if (!(e.is(":visible"))){
e.slideToggle() e.slideToggle();
} }
}.bind(this)) }.bind(this));
jQuery(this.refs.searchbar).typeahead({ jQuery(this.refs.searchbar).typeahead({
source: function(query, process) { source: function(query, process) {
var session = driver.session() var session = driver.session();
var t = '(?i).*' + query + '.*' var t = '(?i).*' + query + '.*';
var data = [] var data = [];
session.run("MATCH (n) WHERE n.name =~ {name} RETURN n LIMIT 10", {name:t}) session.run("MATCH (n) WHERE n.name =~ {name} RETURN n LIMIT 10", {name:t})
.then(function(results){ .then(function(results){
$.each(results.records, function(index, record){ $.each(results.records, function(index, record){
data.push(record._fields[0].properties.name + "#" + record._fields[0].labels[0]) data.push(record._fields[0].properties.name + "#" + record._fields[0].labels[0]);
}) });
session.close() session.close();
return process(data) return process(data);
}) });
}, },
afterSelect: function(selected) { afterSelect: function(selected) {
if (!this.state.pathfindingIsOpen) { if (!this.state.pathfindingIsOpen) {
var statement = "MATCH (n) WHERE n.name = {name} RETURN n" var statement = "MATCH (n) WHERE n.name = {name} RETURN n";
emitter.emit('searchQuery', statement, {name: selected.split("#")[0]}) emitter.emit('searchQuery', statement, {name: selected.split("#")[0]});
} else { } else {
var start = jQuery(this.refs.searchbar).val(); var start = jQuery(this.refs.searchbar).val();
var end = jQuery(this.refs.pathbar).val(); var end = jQuery(this.refs.pathbar).val();
@ -91,57 +91,57 @@ export default class SearchContainer extends Component {
}.bind(this), }.bind(this),
autoSelect: false, autoSelect: false,
updater: function(item){ updater: function(item){
return item.split("#")[0] return item.split("#")[0];
}, },
highlighter: function(item) { highlighter: function(item) {
var parts = item.split("#") var parts = item.split("#");
var query = this.query; var query = this.query;
var icon = ""; var icon = "";
var html = "" var html = "";
switch (parts[1]){ switch (parts[1]){
case "Group": case "Group":
icon = "<i style=\"float:right\" class=\"fa fa-users\"></i>" icon = "<i style=\"float:right\" class=\"fa fa-users\"></i>";
break; break;
case "User": case "User":
icon = "<i style=\"float:right\" class=\"fa fa-user\"></i>" icon = "<i style=\"float:right\" class=\"fa fa-user\"></i>";
break; break;
case "Computer": case "Computer":
icon = "<i style=\"float:right\" class=\"fa fa-desktop\"></i>" icon = "<i style=\"float:right\" class=\"fa fa-desktop\"></i>";
break; break;
case "Domain": case "Domain":
icon = "<i style=\"float:right\" class=\"fa fa-globe\"></i>" icon = "<i style=\"float:right\" class=\"fa fa-globe\"></i>";
break break;
} }
html = '<div>' + parts[0] + ' ' + icon + '</div>' html = '<div>' + parts[0] + ' ' + icon + '</div>';
var reEscQuery = query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); var reEscQuery = query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
var reQuery = new RegExp('(' + reEscQuery + ')', "gi"); var reQuery = new RegExp('(' + reEscQuery + ')', "gi");
var jElem = $(html) var jElem = $(html);
var textNodes = $(jElem.find('*')).add(jElem).contents().filter(function () { return this.nodeType === 3; }); var textNodes = $(jElem.find('*')).add(jElem).contents().filter(function () { return this.nodeType === 3; });
textNodes.replaceWith(function() { textNodes.replaceWith(function() {
return $(this).text().replace(reQuery, '<strong>$1</strong>') return $(this).text().replace(reQuery, '<strong>$1</strong>');
}); });
return jElem.html(); return jElem.html();
} }
} }
) );
jQuery(this.refs.pathbar).typeahead({ jQuery(this.refs.pathbar).typeahead({
source: function(query, process) { source: function(query, process) {
var session = driver.session() var session = driver.session();
var t = '(?i).*' + query + '.*' var t = '(?i).*' + query + '.*';
var data = [] var data = [];
session.run("MATCH (n) WHERE n.name =~ {name} RETURN n LIMIT 10", {name:t}) session.run("MATCH (n) WHERE n.name =~ {name} RETURN n LIMIT 10", {name:t})
.then(function(results){ .then(function(results){
$.each(results.records, function(index, record){ $.each(results.records, function(index, record){
data.push(record._fields[0].properties.name + "#" + record._fields[0].labels[0]) data.push(record._fields[0].properties.name + "#" + record._fields[0].labels[0]);
}) });
session.close() session.close();
return process(data) return process(data);
}) });
}, },
afterSelect: function(selected) { afterSelect: function(selected) {
var start = jQuery(this.refs.searchbar).val(); var start = jQuery(this.refs.searchbar).val();
@ -152,47 +152,47 @@ export default class SearchContainer extends Component {
}.bind(this), }.bind(this),
autoSelect: false, autoSelect: false,
updater: function(item){ updater: function(item){
return item.split("#")[0] return item.split("#")[0];
}, },
highlighter: function(item) { highlighter: function(item) {
var parts = item.split("#") var parts = item.split("#");
var query = this.query; var query = this.query;
var icon = ""; var icon = "";
var html = "" var html = "";
switch (parts[1]){ switch (parts[1]){
case "Group": case "Group":
icon = "<i style=\"float:right\" class=\"fa fa-users\"></i>" icon = "<i class=\"fa fa-users\"></i>";
break; break;
case "User": case "User":
icon = "<i style=\"float:right\" class=\"fa fa-user\"></i>" icon = "<i class=\"fa fa-user\"></i>";
break; break;
case "Computer": case "Computer":
icon = "<i style=\"float:right\" class=\"fa fa-desktop\"></i>" icon = "<i class=\"fa fa-desktop\"></i>";
break; break;
case "Domain": case "Domain":
icon = "<i style=\"float:right\" class=\"fa fa-globe\"></i>" icon = "<i class=\"fa fa-globe\"></i>";
break break;
} }
html = '<div>' + parts[0] + ' ' + icon + '</div>' html = '<div>' + parts[0] + ' ' + icon + '</div>';
var reEscQuery = query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); var reEscQuery = query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
var reQuery = new RegExp('(' + reEscQuery + ')', "gi"); var reQuery = new RegExp('(' + reEscQuery + ')', "gi");
var jElem = $(html) var jElem = $(html);
var textNodes = $(jElem.find('*')).add(jElem).contents().filter(function () { return this.nodeType === 3; }); var textNodes = $(jElem.find('*')).add(jElem).contents().filter(function () { return this.nodeType === 3; });
textNodes.replaceWith(function() { textNodes.replaceWith(function() {
return $(this).text().replace(reQuery, '<strong>$1</strong>') return $(this).text().replace(reQuery, '<strong>$1</strong>');
}); });
return jElem.html(); return jElem.html();
} }
} }
) );
} }
_inputKeyPress(e){ _inputKeyPress(e){
var key = e.keyCode ? e.keyCode : e.which var key = e.keyCode ? e.keyCode : e.which;
var start = jQuery(this.refs.searchbar).val(); var start = jQuery(this.refs.searchbar).val();
var end = jQuery(this.refs.pathbar).val(); var end = jQuery(this.refs.pathbar).val();
var stop = false; var stop = false;
@ -201,17 +201,17 @@ export default class SearchContainer extends Component {
if (!$('.searchSelectorS > ul').is(':hidden')){ if (!$('.searchSelectorS > ul').is(':hidden')){
$('.searchSelectorS > ul li').each(function(i){ $('.searchSelectorS > ul li').each(function(i){
if($(this).hasClass('active')){ if($(this).hasClass('active')){
stop = true stop = true;
} }
}) });
} }
if (!$('.searchSelectorP > ul').is(':hidden')){ if (!$('.searchSelectorP > ul').is(':hidden')){
$('.searchSelectorP > ul li').each(function(i){ $('.searchSelectorP > ul li').each(function(i){
if($(this).hasClass('active')){ if($(this).hasClass('active')){
stop = true stop = true;
} }
}) });
} }
if (stop){ if (stop){
return; return;
@ -219,8 +219,8 @@ export default class SearchContainer extends Component {
if (!this.state.pathfindingIsOpen) { if (!this.state.pathfindingIsOpen) {
if (start !== ""){ if (start !== ""){
var statement = "MATCH (n) WHERE n.name =~ {regex} RETURN n"; var statement = "MATCH (n) WHERE n.name =~ {regex} RETURN n";
var regex = '(?i).*' + start + '.*' var regex = '(?i).*' + start + '.*';
emitter.emit('searchQuery', statement, {regex:regex}) emitter.emit('searchQuery', statement, {regex:regex});
} }
} else { } else {
var start = jQuery(this.refs.searchbar).val(); var start = jQuery(this.refs.searchbar).val();
@ -257,7 +257,7 @@ export default class SearchContainer extends Component {
tooltipTitle="Back" tooltipTitle="Back"
classes="input-group-addon spanfix" classes="input-group-addon spanfix"
click={function(){ click={function(){
emitter.emit('graphBack') emitter.emit('graphBack');
}}> }}>
<Icon glyph="step-backward" extraClass="menuglyph" /> <Icon glyph="step-backward" extraClass="menuglyph" />
</GlyphiconSpan> </GlyphiconSpan>
@ -289,6 +289,6 @@ export default class SearchContainer extends Component {
<TabContainer /> <TabContainer />
</div> </div>
</div> </div>
) );
} }
} }

View File

@ -1,344 +1,386 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import NodeALink from './NodeALink' import NodeALink from './NodeALink';
import PropTypes from 'prop-types' import PropTypes from 'prop-types';
export default class ComputerNodeData extends Component { export default class ComputerNodeData extends Component {
constructor(){ constructor(){
super(); super();
this.state = { this.state = {
label: "", label: "",
os: "None", explicitAdmins: -1,
unconstrained: "None", unrolledAdmins: -1,
explicitAdmins: -1, firstDegreeGroupMembership: -1,
unrolledAdmins: -1, unrolledGroupMembership: -1,
firstDegreeGroupMembership: -1, firstDegreeLocalAdmin: -1,
unrolledGroupMembership: -1, groupDelegatedLocalAdmin: -1,
firstDegreeLocalAdmin: -1, derivativeLocalAdmin: -1,
groupDelegatedLocalAdmin: -1, sessions: -1,
derivativeLocalAdmin: -1, firstdegreeControl: -1,
sessions: -1, groupDelegatedControl: -1,
firstdegreeControl: -1, transitiveControl: -1,
groupDelegatedControl: -1, derivativeLocalAdmins: -1,
transitiveControl: -1, driversessions: [],
derivativeLocalAdmins: -1, propertyMap: {}
driversessions: [] };
}
emitter.on('computerNodeClicked', this.getNodeData.bind(this)); emitter.on('computerNodeClicked', this.getNodeData.bind(this));
} }
getNodeData(payload){ getNodeData(payload){
$.each(this.state.driversessions, function(index, record){ $.each(this.state.driversessions, function(index, record){
record.close(); record.close();
}) });
this.setState({ this.setState({
label: payload, label: payload,
os: "None", explicitAdmins: -1,
unconstrained: "None", unrolledAdmins: -1,
explicitAdmins: -1, firstDegreeGroupMembership: -1,
unrolledAdmins: -1, unrolledGroupMembership: -1,
firstDegreeGroupMembership: -1, sessions: -1,
unrolledGroupMembership: -1, firstDegreeLocalAdmin: -1,
sessions: -1, groupDelegatedLocalAdmin: -1,
firstDegreeLocalAdmin: -1, derivativeLocalAdmin: -1,
groupDelegatedLocalAdmin: -1, firstdegreeControl: -1,
derivativeLocalAdmin: -1, groupDelegatedControl: -1,
firstdegreeControl: -1, transitiveControl: -1,
groupDelegatedControl: -1, derivativeLocalAdmins: -1,
transitiveControl: -1, propertyMap: {}
derivativeLocalAdmins: -1 });
})
var s1 = driver.session() var s1 = driver.session();
var s2 = driver.session() var s2 = driver.session();
var s3 = driver.session() var s3 = driver.session();
var s4 = driver.session() var s4 = driver.session();
var s5 = driver.session() var s5 = driver.session();
var s6 = driver.session() var s6 = driver.session();
var s7 = driver.session() var s7 = driver.session();
var s8 = driver.session() var s8 = driver.session();
var s9 = driver.session() var s9 = driver.session();
var s10 = driver.session() var s10 = driver.session();
var s11 = driver.session() var s11 = driver.session();
var s12 = driver.session() var s12 = driver.session();
var s13 = driver.session() var s13 = driver.session();
s1.run("MATCH (a)-[b:AdminTo]->(c:Computer {name:{name}}) RETURN count(a)", {name:payload}) var propCollection = driver.session();
.then(function(result){ propCollection.run("MATCH (c:Computer {name:{name}}) RETURN c", {name:payload})
this.setState({'explicitAdmins':result.records[0]._fields[0].low}) .then(function(result){
s1.close() var properties = result.records[0]._fields[0].properties;
}.bind(this)) this.setState({propertyMap: properties});
propCollection.close();
}.bind(this));
s2.run("MATCH p=(n:User)-[r:MemberOf|AdminTo*1..]->(m:Computer {name:{name}}) RETURN count(distinct(n))", {name:payload}) s1.run("MATCH (a)-[b:AdminTo]->(c:Computer {name:{name}}) RETURN count(a)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'unrolledAdmins':result.records[0]._fields[0].low}) this.setState({'explicitAdmins':result.records[0]._fields[0].low});
s2.close() s1.close();
}.bind(this)) }.bind(this));
s3.run("MATCH (m:Computer {name:{name}}), (n:Computer), (m)-[r:AdminTo]->(n) RETURN count(distinct(m))", {name:payload}) s2.run("MATCH p=(n:User)-[r:MemberOf|AdminTo*1..]->(m:Computer {name:{name}}) RETURN count(distinct(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'firstDegreeLocalAdmin':result.records[0]._fields[0].low}) this.setState({'unrolledAdmins':result.records[0]._fields[0].low});
s3.close() s2.close();
}.bind(this)) }.bind(this));
s4.run("MATCH p=(n:Computer {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AdminTo]->(c2:Computer) RETURN count(c2)", {name:payload}) s3.run("MATCH (m:Computer {name:{name}}), (n:Computer), (m)-[r:AdminTo]->(n) RETURN count(distinct(m))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'groupDelegatedLocalAdmin':result.records[0]._fields[0].low}) this.setState({'firstDegreeLocalAdmin':result.records[0]._fields[0].low});
s4.close() s3.close();
}.bind(this)) }.bind(this));
s5.run("MATCH (n:Computer {name:{name}}), (m:Computer), p=shortestPath((n)-[r:AdminTo|MemberOf*1..]->(m)) RETURN count(distinct(m))", {name:payload}) s4.run("MATCH p=(n:Computer {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AdminTo]->(c2:Computer) RETURN count(c2)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'derivativeLocalAdmin':result.records[0]._fields[0].low}) this.setState({'groupDelegatedLocalAdmin':result.records[0]._fields[0].low});
s5.close() s4.close();
}.bind(this)) }.bind(this));
s6.run("MATCH (n:Computer {name:{name}}),(target:Group), (n)-[r:MemberOf]->(target) RETURN count(target)", {name:payload}) s5.run("MATCH (n:Computer {name:{name}}), (m:Computer) WHERE NOT m.name={name} MATCH p=shortestPath((n)-[r:AdminTo|MemberOf*1..]->(m)) RETURN count(distinct(m))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'firstDegreeGroupMembership':result.records[0]._fields[0].low}) this.setState({'derivativeLocalAdmin':result.records[0]._fields[0].low});
s6.close() s5.close();
}.bind(this)) }.bind(this));
s7.run("MATCH p = (c:Computer {name:{name}})-[r:MemberOf*1..]->(g:Group) RETURN COUNT(DISTINCT(g))", {name:payload}) s6.run("MATCH (n:Computer {name:{name}}),(target:Group), (n)-[r:MemberOf]->(target) RETURN count(target)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'unrolledGroupMembership':result.records[0]._fields[0].low}) this.setState({'firstDegreeGroupMembership':result.records[0]._fields[0].low});
s7.close() s6.close();
}.bind(this)) }.bind(this));
s8.run("MATCH (m:Computer {name:{name}})-[r:HasSession]->(n:User) WITH n,r,m WHERE NOT n.name ENDS WITH '$' RETURN COUNT(DISTINCT(n))", {name:payload}) s7.run("MATCH p = (c:Computer {name:{name}})-[r:MemberOf*1..]->(g:Group) RETURN COUNT(DISTINCT(g))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'sessions':result.records[0]._fields[0].low}) this.setState({'unrolledGroupMembership':result.records[0]._fields[0].low});
s8.close() s7.close();
}.bind(this)) }.bind(this));
s9.run("MATCH p = shortestPath((n)-[r:AdminTo|MemberOf|HasSession*1..]->(m:Computer {name:{name}})) RETURN COUNT(DISTINCT(n))", {name:payload}) s8.run("MATCH (m:Computer {name:{name}})-[r:HasSession]->(n:User) WITH n,r,m WHERE NOT n.name ENDS WITH '$' RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'derivativeLocalAdmins':result.records[0]._fields[0].low}) this.setState({'sessions':result.records[0]._fields[0].low});
s9.close() s8.close();
}.bind(this)) }.bind(this));
s10.run("MATCH p = (c:Computer {name:{name}})-[r:MemberOf*1..]->(g:Group) WHERE NOT g.domain = c.domain RETURN COUNT(DISTINCT(g))", {name:payload}) s9.run("MATCH (n) WHERE NOT n.name={name} WITH n MATCH p = shortestPath((n)-[r:AdminTo|MemberOf|HasSession*1..]->(m:Computer {name:{name}})) RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'foreignGroupMembership':result.records[0]._fields[0].low}) this.setState({'derivativeLocalAdmins':result.records[0]._fields[0].low});
s10.close() s9.close();
}.bind(this)) }.bind(this));
s11.run("MATCH p = (c:Computer {name:{name}})-[r:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN COUNT(DISTINCT(n))", {name:payload}) s10.run("MATCH p = (c:Computer {name:{name}})-[r:MemberOf*1..]->(g:Group) WHERE NOT g.domain = c.domain RETURN COUNT(DISTINCT(g))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'firstdegreeControl':result.records[0]._fields[0].low}) this.setState({'foreignGroupMembership':result.records[0]._fields[0].low});
s11.close() s10.close();
}.bind(this)) }.bind(this));
s12.run("MATCH p = (c:Computer {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN COUNT(DISTINCT(n))", {name:payload}) s11.run("MATCH p = (c:Computer {name:{name}})-[r:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'groupDelegatedControl':result.records[0]._fields[0].low}) this.setState({'firstdegreeControl':result.records[0]._fields[0].low});
s12.close() s11.close();
}.bind(this)) }.bind(this));
s13.run("MATCH p = shortestPath((c:Computer {name:{name}})-[r:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) RETURN COUNT(DISTINCT(n))", {name:payload}) s12.run("MATCH p = (c:Computer {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'transitiveControl':result.records[0]._fields[0].low}) this.setState({'groupDelegatedControl':result.records[0]._fields[0].low});
s13.close() s12.close();
}.bind(this)) }.bind(this));
this.setState({'driversessions': [s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13]})
}
render() { s13.run("MATCH (n) WHERE NOT n.name={name} WITH n MATCH p = shortestPath((c:Computer {name:{name}})-[r:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) RETURN COUNT(DISTINCT(n))", {name:payload})
return ( .then(function(result){
<div className={this.props.visible ? "" : "displaynone"}> this.setState({'transitiveControl':result.records[0]._fields[0].low});
<dl className='dl-horizontal'> s13.close();
<h4>Node Info</h4> }.bind(this));
<dt>
Name this.setState({'driversessions': [s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,propCollection]});
</dt> }
<dd>
{this.state.label} convertToDisplayProp(propName){
</dd> var obj = this.state.propertyMap[propName];
<dt> var type = typeof obj;
OS if (type === 'undefined'){
</dt> return "No Data";
<dd> }else if (obj.hasOwnProperty('low')){
{this.state.os} var t = obj.low;
</dd> if (t === 0){
<dt> return "Never";
Allows Unconstrained Delegation }else{
</dt> return new Date(obj.low * 1000).toUTCString();
<dd> }
{this.state.unconstrained} }else if (type === 'boolean'){
</dd> return obj.toString().toTitleCase();
<dt> }else if (obj === ""){
Sessions return "None";
</dt> }else{
<dd> return obj;
<NodeALink }
ready={this.state.sessions !== -1} }
value={this.state.sessions}
click={function(){ render() {
emitter.emit('query', return (
"MATCH (m:Computer {name:{name}})-[r:HasSession]->(n:User) WITH n,r,m WHERE NOT n.name ENDS WITH '$' RETURN n,r,m", {name: this.state.label}) <div className={this.props.visible ? "" : "displaynone"}>
}.bind(this)} /> <dl className='dl-horizontal'>
</dd> <h4>Node Info</h4>
<br /> <dt>
<h4>Local Admins</h4> Name
<dt> </dt>
Explicit Admins <dd>
</dt> {this.state.label}
<dd> </dd>
<NodeALink <dt>
ready={this.state.explicitAdmins !== -1} OS
value={this.state.explicitAdmins} </dt>
click={function(){ <dd>
emitter.emit('query', {this.convertToDisplayProp("OperatingSystem")}
"MATCH (n)-[r:AdminTo]->(m:Computer {name:{name}}) RETURN n,r,m",{name: this.state.label}) </dd>
}.bind(this)} /> <dt>
</dd> Enabled
<dt> </dt>
Unrolled Admins <dd>
</dt> {this.convertToDisplayProp("Enabled")}
<dd> </dd>
<NodeALink <dt>
ready={this.state.unrolledAdmins !== -1} Allows Unconstrained Delegation
value={this.state.unrolledAdmins} </dt>
click={function(){ <dd>
emitter.emit('query', {this.convertToDisplayProp("UnconstrainedDelegation")}
"MATCH p = (n:User)-[r:MemberOf*1..]->(g:Group)-[r2:AdminTo]->(c:Computer {name:{name}}) RETURN p", </dd>
{name: this.state.label}, <dt>
this.state.label) Sessions
}.bind(this)} /> </dt>
</dd> <dd>
<dt> <NodeALink
Derivative Local Admins ready={this.state.sessions !== -1}
</dt> value={this.state.sessions}
<dd> click={function(){
<NodeALink emitter.emit('query',
ready={this.state.derivativeLocalAdmins !== -1} "MATCH (m:Computer {name:{name}})-[r:HasSession]->(n:User) WITH n,r,m WHERE NOT n.name ENDS WITH '$' RETURN n,r,m", {name: this.state.label});
value={this.state.derivativeLocalAdmins} }.bind(this)}
click={function(){ />
emitter.emit('query', </dd>
"MATCH p = shortestPath((n)-[r:AdminTo|MemberOf|HasSession*1..]->(m:Computer {name:{name}})) RETURN p",{name: this.state.label}, this.state.label) <h4>Local Admins</h4>
}.bind(this)} /> <dt>
</dd> Explicit Admins
<br /> </dt>
<h4>Group Memberships</h4> <dd>
<dt> <NodeALink
First Degree Group Membership ready={this.state.explicitAdmins !== -1}
</dt> value={this.state.explicitAdmins}
<dd> click={function(){
<NodeALink emitter.emit('query',
ready={this.state.firstDegreeGroupMembership !== -1} "MATCH (n)-[r:AdminTo]->(m:Computer {name:{name}}) RETURN n,r,m",{name: this.state.label});
value={this.state.firstDegreeGroupMembership} }.bind(this)}
click={function(){ />
emitter.emit('query', </dd>
"MATCH (n:Computer {name:{name}}),(m:Group), (n)-[r:MemberOf]->(m) RETURN n,r,m",{name: this.state.label}, this.state.label) <dt>
}.bind(this)} /> Unrolled Admins
</dd> </dt>
<dt> <dd>
Unrolled Group Membership <NodeALink
</dt> ready={this.state.unrolledAdmins !== -1}
<dd> value={this.state.unrolledAdmins}
<NodeALink click={function(){
ready={this.state.unrolledGroupMembership !== -1} emitter.emit('query',
value={this.state.unrolledGroupMembership} "MATCH p = (n:User)-[r:MemberOf*1..]->(g:Group)-[r2:AdminTo]->(c:Computer {name:{name}}) RETURN p",
click={function(){ {name: this.state.label},
emitter.emit('query', this.state.label);
"MATCH p = (n:Computer {name:{name}})-[r:MemberOf*1..]->(m:Group) RETURN p",{name: this.state.label}, this.state.label) }.bind(this)}
}.bind(this)} /> />
</dd> </dd>
<dt> <dt>
Foreign Group Membership Derivative Local Admins
</dt> </dt>
<dd> <dd>
<NodeALink <NodeALink
ready={this.state.foreignGroupMembership !== -1} ready={this.state.derivativeLocalAdmins !== -1}
value={this.state.foreignGroupMembership} value={this.state.derivativeLocalAdmins}
click={function(){ click={function(){
emitter.emit('query', emitter.emit('query',
"MATCH p = (c:Computer {name:{name}})-[r:MemberOf*1..]->(g:Group) WHERE NOT g.domain = c.domain RETURN p",{name: this.state.label}, this.state.label) "MATCH (n) WHERE NOT n.name={name} WITH n MATCH p = shortestPath((n)-[r:AdminTo|MemberOf|HasSession*1..]->(m:Computer {name:{name}})) RETURN p",{name: this.state.label}, this.state.label);
}.bind(this)} /> }.bind(this)}
</dd> />
<br /> </dd>
<h4>Local Admin Rights</h4> <h4>Group Memberships</h4>
<dt> <dt>
First Degree Local Admin First Degree Group Membership
</dt> </dt>
<dd> <dd>
<NodeALink <NodeALink
ready={this.state.firstDegreeLocalAdmin !== -1} ready={this.state.firstDegreeGroupMembership !== -1}
value={this.state.firstDegreeLocalAdmin} value={this.state.firstDegreeGroupMembership}
click={function(){ click={function(){
emitter.emit('query', emitter.emit('query',
"MATCH (n:Computer {name:{name}}), (m:Computer), p=(n)-[r:AdminTo]->(m) RETURN p",{name: this.state.label}, this.state.label) "MATCH (n:Computer {name:{name}}),(m:Group), (n)-[r:MemberOf]->(m) RETURN n,r,m",{name: this.state.label}, this.state.label);
}.bind(this)} /> }.bind(this)}
</dd> />
<dt> </dd>
Group Delegated Local Admin <dt>
</dt> Unrolled Group Membership
<dd> </dt>
<NodeALink <dd>
ready={this.state.groupDelegatedLocalAdmin !== -1} <NodeALink
value={this.state.groupDelegatedLocalAdmin} ready={this.state.unrolledGroupMembership !== -1}
click={function(){ value={this.state.unrolledGroupMembership}
emitter.emit('query', click={function(){
"MATCH p=(n:Computer {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AdminTo]->(m:Computer) RETURN p",{name: this.state.label}, this.state.label) emitter.emit('query',
}.bind(this)} /> "MATCH p = (n:Computer {name:{name}})-[r:MemberOf*1..]->(m:Group) RETURN p",{name: this.state.label}, this.state.label);
</dd> }.bind(this)}
<dt> />
Derivative Local Admin </dd>
</dt> <dt>
<dd> Foreign Group Membership
<NodeALink </dt>
ready={this.state.derivativeLocalAdmin !== -1} <dd>
value={this.state.derivativeLocalAdmin} <NodeALink
click={function(){ ready={this.state.foreignGroupMembership !== -1}
emitter.emit('query', value={this.state.foreignGroupMembership}
"MATCH p = shortestPath((c1:Computer {name:{name}})-[r:AdminTo|MemberOf|HasSession*1..]->(c:Computer)) RETURN p",{name: this.state.label}, this.state.label) click={function(){
}.bind(this)} /> emitter.emit('query',
</dd> "MATCH p = (c:Computer {name:{name}})-[r:MemberOf*1..]->(g:Group) WHERE NOT g.domain = c.domain RETURN p",{name: this.state.label}, this.state.label);
<br /> }.bind(this)}
<h4>Outbound Object Control</h4> />
<dt> </dd>
First Degree Object Control <h4>Local Admin Rights</h4>
</dt> <dt>
<dd> First Degree Local Admin
<NodeALink </dt>
ready={this.state.firstdegreeControl !== -1} <dd>
value={this.state.firstdegreeControl} <NodeALink
click={function(){ ready={this.state.firstDegreeLocalAdmin !== -1}
emitter.emit('query', "MATCH p = (c:Computer {name:{name}})-[r1:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN p", {name:this.state.label}) value={this.state.firstDegreeLocalAdmin}
}.bind(this)} /> click={function(){
</dd> emitter.emit('query',
<dt> "MATCH (n:Computer {name:{name}}), (m:Computer), p=(n)-[r:AdminTo]->(m) RETURN p",{name: this.state.label}, this.state.label);
Group Delegated Object Control }.bind(this)}
</dt> />
<dd> </dd>
<NodeALink <dt>
ready={this.state.groupDelegatedControl !== -1} Group Delegated Local Admin
value={this.state.groupDelegatedControl} </dt>
click={function(){ <dd>
emitter.emit('query', "MATCH p = (c:Computer {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN p", {name:this.state.label} <NodeALink
,this.state.label) ready={this.state.groupDelegatedLocalAdmin !== -1}
}.bind(this)} /> value={this.state.groupDelegatedLocalAdmin}
</dd> click={function(){
<dt> emitter.emit('query',
Transitive Object Control "MATCH p=(n:Computer {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AdminTo]->(m:Computer) RETURN p",{name: this.state.label}, this.state.label);
</dt> }.bind(this)}
<dd> />
<NodeALink </dd>
ready={this.state.transitiveControl !== -1} <dt>
value={this.state.transitiveControl} Derivative Local Admin
click={function(){ </dt>
emitter.emit('query', "MATCH p = shortestPath((c:Computer {name:{name}})-[r1:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) RETURN p", {name:this.state.label} <dd>
,this.state.label) <NodeALink
}.bind(this)} /> ready={this.state.derivativeLocalAdmin !== -1}
</dd> value={this.state.derivativeLocalAdmin}
</dl> click={function(){
</div> emitter.emit('query',
); "MATCH (c:Computer) WHERE NOT c.name={name} WITH c MATCH p = shortestPath((c1:Computer {name:{name}})-[r:AdminTo|MemberOf|HasSession*1..]->(c)) RETURN p",{name: this.state.label}, this.state.label);
} }.bind(this)}
/>
</dd>
<h4>Outbound Object Control</h4>
<dt>
First Degree Object Control
</dt>
<dd>
<NodeALink
ready={this.state.firstdegreeControl !== -1}
value={this.state.firstdegreeControl}
click={function(){
emitter.emit('query', "MATCH p = (c:Computer {name:{name}})-[r1:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN p", {name:this.state.label});
}.bind(this)}
/>
</dd>
<dt>
Group Delegated Object Control
</dt>
<dd>
<NodeALink
ready={this.state.groupDelegatedControl !== -1}
value={this.state.groupDelegatedControl}
click={function(){
emitter.emit('query', "MATCH p = (c:Computer {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN p", {name:this.state.label}
,this.state.label);
}.bind(this)}
/>
</dd>
<dt>
Transitive Object Control
</dt>
<dd>
<NodeALink
ready={this.state.transitiveControl !== -1}
value={this.state.transitiveControl}
click={function(){
emitter.emit('query', "MATCH (n) WHERE NOT n.name={name} WITH n MATCH p = shortestPath((c:Computer {name:{name}})-[r1:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) RETURN p", {name:this.state.label}
,this.state.label);
}.bind(this)}
/>
</dd>
</dl>
</div>
);
}
} }
ComputerNodeData.propTypes= { ComputerNodeData.propTypes= {
visible : React.PropTypes.bool.isRequired visible : React.PropTypes.bool.isRequired
} };

View File

@ -2,121 +2,134 @@ import React, { Component } from 'react';
import LogoutModal from 'modals/LogoutModal'; import LogoutModal from 'modals/LogoutModal';
export default class DatabaseDataDisplay extends Component { export default class DatabaseDataDisplay extends Component {
constructor(){ constructor(){
super() super();
this.state = { this.state = {
url: appStore.databaseInfo.url, url: appStore.databaseInfo.url,
user: appStore.databaseInfo.user, user: appStore.databaseInfo.user,
num_users: 'Refreshing', num_users: 'Refreshing',
num_computers: 'Refreshing', num_computers: 'Refreshing',
num_groups: 'Refreshing', num_groups: 'Refreshing',
num_relationships: 'Refreshing', num_relationships: 'Refreshing',
num_sessions: 'Refreshing', num_sessions: 'Refreshing',
interval: null num_acls: 'Refreshing',
} interval: null
} };
}
componentDidMount() { componentDidMount() {
this.refreshDBData() this.refreshDBData();
var x = setInterval(function(){ emitter.on('hideDBClearModal', this.refreshDBData.bind(this));
this.refreshDBData() emitter.on('refreshDBData', this.refreshDBData.bind(this));
}.bind(this), 60000); this.createInterval();
this.setState({ }
interval: x
})
emitter.on('hideDBClearModal', this.refreshDBData.bind(this))
emitter.on('refreshDBData', this.refreshDBData.bind(this))
}
componentWillUnmount() { componentWillUnmount() {
clearInterval(this.state.interval) clearInterval(this.state.interval);
this.setState({ this.setState({
interval: null, interval: null,
session: null session: null
}) });
} }
toggleLogoutModal(){ createInterval(){
emitter.emit('showLogout'); var x = setInterval(function(){
} this.refreshDBData();
}.bind(this), 60000);
this.setState({
interval: x
});
};
toggleDBWarnModal(){ toggleLogoutModal(){
emitter.emit('openDBWarnModal') emitter.emit('showLogout');
} }
toggleSessionClearModal(){ toggleDBWarnModal(){
emitter.emit('openSessionClearModal') emitter.emit('openDBWarnModal');
} }
render() { toggleSessionClearModal(){
return ( emitter.emit('openSessionClearModal');
<div> }
<h3>Database Info</h3>
<dl className="dl-horizontal dl-horizontal-fix">
<dt>DB Address</dt>
<dd>{this.state.url}</dd>
<dt>DB User</dt>
<dd>{this.state.user}</dd>
<dt>Users</dt>
<dd>{this.state.num_users}</dd>
<dt>Computers</dt>
<dd>{this.state.num_computers}</dd>
<dt>Groups</dt>
<dd>{this.state.num_groups}</dd>
<dt>Sessions</dt>
<dd>{this.state.num_sessions}</dd>
<dt>Relationships</dt>
<dd>{this.state.num_relationships}</dd>
</dl>
<div className="text-center"> refreshDBData(){
<div className="btn-group btn-group-sm dbbuttons"> var s1 = driver.session();
<button type="button" className="btn btn-success" onClick={function(){this.refreshDBData()}.bind(this)}>Refresh DB Stats</button> var s2 = driver.session();
<button type="button" className="btn btn-info" onClick={this.toggleSessionClearModal}>Clear Sessions</button> var s3 = driver.session();
<button type="button" className="btn btn-warning" onClick={this.toggleLogoutModal}>Log Out/Switch DB</button> var s4 = driver.session();
<button type="button" className="btn btn-danger" onClick={this.toggleDBWarnModal}>Clear Database</button> var s5 = driver.session();
</div> var s6 = driver.session();
</div>
</div>
);
}
refreshDBData(){ s1.run("MATCH (n:User) WHERE NOT n.name ENDS WITH '$' RETURN count(n)")
var s1 = driver.session() .then(function(result){
var s2 = driver.session() this.setState({'num_users':result.records[0]._fields[0].low});
var s3 = driver.session() s1.close();
var s4 = driver.session() }.bind(this));
var s5 = driver.session()
s2.run("MATCH (n:Group) RETURN count(n)")
.then(function(result){
this.setState({'num_groups':result.records[0]._fields[0].low});
s2.close();
}.bind(this));
s3.run("MATCH (n:Computer) RETURN count(n)")
.then(function(result){
this.setState({'num_computers':result.records[0]._fields[0].low});
s3.close();
}.bind(this));
s1.run("MATCH (n:User) WHERE NOT n.name ENDS WITH '$' RETURN count(n)") s4.run("MATCH ()-[r:HasSession]->() RETURN count(r)")
.then(function(result){ .then(function(result){
this.setState({'num_users':result.records[0]._fields[0].low}) this.setState({'num_sessions':result.records[0]._fields[0].low});
s1.close() s4.close();
}.bind(this)) }.bind(this));
s2.run("MATCH (n:Group) RETURN count(n)") s6.run("MATCH ()-[r {isACL: true}]->() RETURN count(r)")
.then(function(result){ .then(function(result){
this.setState({'num_groups':result.records[0]._fields[0].low}) this.setState({'num_acls':result.records[0]._fields[0].low});
s2.close() s6.close();
}.bind(this)) }.bind(this));
s3.run("MATCH (n:Computer) RETURN count(n)")
.then(function(result){
this.setState({'num_computers':result.records[0]._fields[0].low})
s3.close()
}.bind(this))
s4.run("MATCH ()-[r:HasSession]->() RETURN count(r)") s5.run("MATCH ()-[r]->() RETURN count(r)")
.then(function(result){ .then(function(result){
this.setState({'num_sessions':result.records[0]._fields[0].low}) this.setState({'num_relationships':result.records[0]._fields[0].low});
s4.close() s5.close();
}.bind(this)) }.bind(this));
}
render() {
return (
<div>
<h3>Database Info</h3>
<dl className="dl-horizontal dl-horizontal-fix">
<dt>DB Address</dt>
<dd>{this.state.url}</dd>
<dt>DB User</dt>
<dd>{this.state.user}</dd>
<dt>Users</dt>
<dd>{this.state.num_users}</dd>
<dt>Computers</dt>
<dd>{this.state.num_computers}</dd>
<dt>Groups</dt>
<dd>{this.state.num_groups}</dd>
<dt>Sessions</dt>
<dd>{this.state.num_sessions}</dd>
<dt>ACLs</dt>
<dd>{this.state.num_acls}</dd>
<dt>Relationships</dt>
<dd>{this.state.num_relationships}</dd>
</dl>
s5.run("MATCH ()-[r]->() RETURN count(r)") <div className="text-center">
.then(function(result){ <div className="btn-group btn-group-sm dbbuttons">
this.setState({'num_relationships':result.records[0]._fields[0].low}) <button type="button" className="btn btn-success" onClick={function(){this.refreshDBData();}.bind(this)}>Refresh DB Stats</button>
s5.close() <button type="button" className="btn btn-info" onClick={this.toggleSessionClearModal}>Clear Sessions</button>
}.bind(this)) <button type="button" className="btn btn-warning" onClick={this.toggleLogoutModal}>Log Out/Switch DB</button>
} <button type="button" className="btn btn-danger" onClick={this.toggleDBWarnModal}>Clear Database</button>
</div>
</div>
</div>
);
}
} }

View File

@ -1,233 +1,243 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import NodeALink from './NodeALink.jsx' import NodeALink from './NodeALink.jsx';
import LoadLabel from './LoadLabel.jsx' import LoadLabel from './LoadLabel.jsx';
import PropTypes from 'prop-types' import PropTypes from 'prop-types';
export default class DomainNodeData extends Component { export default class DomainNodeData extends Component {
constructor(){ constructor(){
super(); super();
this.state = { this.state = {
label: "", label: "",
users: -1, users: -1,
groups: -1, groups: -1,
computers: -1, computers: -1,
foreignGroups: -1, foreignGroups: -1,
foreignUsers: -1, foreignUsers: -1,
firstDegreeOutboundTrusts: -1, firstDegreeOutboundTrusts: -1,
effectiveOutboundTrusts: -1, effectiveOutboundTrusts: -1,
firstDegreeInboundTrusts: -1, firstDegreeInboundTrusts: -1,
effectiveInboundTrusts: -1, effectiveInboundTrusts: -1,
driversessions: [] driversessions: []
} };
emitter.on('domainNodeClicked', this.getNodeData.bind(this)); emitter.on('domainNodeClicked', this.getNodeData.bind(this));
} }
getNodeData(payload){ getNodeData(payload){
$.each(this.state.driversessions, function(index, record){ $.each(this.state.driversessions, function(index, record){
record.close(); record.close();
}) });
this.setState({ this.setState({
label: payload, label: payload,
users: -1, users: -1,
groups: -1, groups: -1,
computers: -1, computers: -1,
foreignGroups: -1, foreignGroups: -1,
foreignUsers: -1, foreignUsers: -1,
firstDegreeOutboundTrusts: -1, firstDegreeOutboundTrusts: -1,
effectiveOutboundTrusts: -1, effectiveOutboundTrusts: -1,
firstDegreeInboundTrusts: -1, firstDegreeInboundTrusts: -1,
effectiveInboundTrusts: -1 effectiveInboundTrusts: -1
}) });
var s1 = driver.session() var s1 = driver.session();
var s2 = driver.session() var s2 = driver.session();
var s3 = driver.session() var s3 = driver.session();
var s4 = driver.session() var s4 = driver.session();
var s5 = driver.session() var s5 = driver.session();
var s6 = driver.session() var s6 = driver.session();
var s7 = driver.session() var s7 = driver.session();
var s8 = driver.session() var s8 = driver.session();
var s9 = driver.session() var s9 = driver.session();
s1.run("MATCH (a:User) WHERE a.name ENDS WITH ('@' + {name}) RETURN COUNT(a)", {name:payload}) s1.run("MATCH (a:User) WHERE a.name ENDS WITH ('@' + {name}) RETURN COUNT(a)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'users':result.records[0]._fields[0].low}) this.setState({'users':result.records[0]._fields[0].low});
s1.close() s1.close();
}.bind(this)) }.bind(this));
s2.run("MATCH (a:Group) WHERE a.name ENDS WITH ('@' + {name}) RETURN COUNT(a)", {name:payload}) s2.run("MATCH (a:Group) WHERE a.name ENDS WITH ('@' + {name}) RETURN COUNT(a)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'groups':result.records[0]._fields[0].low}) this.setState({'groups':result.records[0]._fields[0].low});
s2.close() s2.close();
}.bind(this)) }.bind(this));
s3.run("MATCH (n:Computer) WHERE n.name ENDS WITH {name} WITH n WHERE size(split(n.name,'.')) - size(split({name},'.')) = 1 RETURN count(n)", {name:payload}) s3.run("MATCH (n:Computer) WHERE n.name ENDS WITH {name} WITH n WHERE size(split(n.name,'.')) - size(split({name},'.')) = 1 RETURN count(n)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'computers':result.records[0]._fields[0].low}) this.setState({'computers':result.records[0]._fields[0].low});
s3.close() s3.close();
}.bind(this)) }.bind(this));
s4.run("MATCH (a:Group) WHERE NOT a.name ENDS WITH ('@' + {name}) WITH a MATCH (b:Group) WHERE b.name ENDS WITH ('@' + {name}) WITH a,b MATCH (a)-[r:MemberOf]->(b) RETURN count(a)", {name:payload}) s4.run("MATCH (a:Group) WHERE NOT a.name ENDS WITH ('@' + {name}) WITH a MATCH (b:Group) WHERE b.name ENDS WITH ('@' + {name}) WITH a,b MATCH (a)-[r:MemberOf]->(b) RETURN count(a)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'foreignGroups':result.records[0]._fields[0].low}) this.setState({'foreignGroups':result.records[0]._fields[0].low});
s4.close() s4.close();
}.bind(this)) }.bind(this));
s5.run("MATCH (a:User) WHERE NOT a.name ENDS WITH ('@' + {name}) WITH a MATCH (b:Group) WHERE b.name ENDS WITH ('@' + {name}) WITH a,b MATCH (a)-[r:MemberOf]->(b) RETURN count(a)", {name:payload}) s5.run("MATCH (a:User) WHERE NOT a.name ENDS WITH ('@' + {name}) WITH a MATCH (b:Group) WHERE b.name ENDS WITH ('@' + {name}) WITH a,b MATCH (a)-[r:MemberOf]->(b) RETURN count(a)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'foreignUsers':result.records[0]._fields[0].low}) this.setState({'foreignUsers':result.records[0]._fields[0].low});
s5.close() s5.close();
}.bind(this)) }.bind(this));
s6.run("MATCH (a:Domain {name:{name}})<-[r:TrustedBy]-(b:Domain) RETURN count(b)", {name:payload}) s6.run("MATCH (a:Domain {name:{name}})<-[r:TrustedBy]-(b:Domain) RETURN count(b)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'firstDegreeInboundTrusts':result.records[0]._fields[0].low}) this.setState({'firstDegreeInboundTrusts':result.records[0]._fields[0].low});
s6.close() s6.close();
}.bind(this)) }.bind(this));
s7.run("MATCH (a:Domain {name:{name}})-[r:TrustedBy]->(b:Domain) RETURN count(b)", {name:payload}) s7.run("MATCH (a:Domain {name:{name}})-[r:TrustedBy]->(b:Domain) RETURN count(b)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'firstDegreeOutboundTrusts':result.records[0]._fields[0].low}) this.setState({'firstDegreeOutboundTrusts':result.records[0]._fields[0].low});
s7.close() s7.close();
}.bind(this)) }.bind(this));
s8.run("MATCH p=shortestPath((a:Domain {name:{name}})<-[r:TrustedBy*1..]-(b:Domain)) RETURN count(b)", {name:payload}) s8.run("MATCH (b:Domain) WHERE NOT b.name={name} WITH b MATCH p=shortestPath((a:Domain {name:{name}})<-[r:TrustedBy*1..]-(b)) RETURN count(b)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'effectiveInboundTrusts':result.records[0]._fields[0].low}) this.setState({'effectiveInboundTrusts':result.records[0]._fields[0].low});
s8.close() s8.close();
}.bind(this)) }.bind(this));
s9.run("MATCH p=shortestPath((a:Domain {name:{name}})-[r:TrustedBy*1..]->(b:Domain)) RETURN count(b)", {name:payload}) s9.run("MATCH (b:Domain) WHERE NOT b.name={name} MATCH p=shortestPath((a:Domain {name:{name}})-[r:TrustedBy*1..]->(b)) RETURN count(b)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'effectiveOutboundTrusts':result.records[0]._fields[0].low}) this.setState({'effectiveOutboundTrusts':result.records[0]._fields[0].low});
s9.close() s9.close();
}.bind(this)) }.bind(this));
this.setState({'driversessions': [s1,s2,s3,s4,s5,s6,s7,s8,s9]}) this.setState({'driversessions': [s1,s2,s3,s4,s5,s6,s7,s8,s9]});
} }
render() { render() {
return ( return (
<div className={this.props.visible ? "" : "displaynone"}> <div className={this.props.visible ? "" : "displaynone"}>
<dl className='dl-horizontal'> <dl className='dl-horizontal'>
<dt> <dt>
Node Node
</dt> </dt>
<dd> <dd>
{this.state.label} {this.state.label}
</dd> </dd>
<br /> <br />
<dt> <dt>
Users Users
</dt> </dt>
<dd> <dd>
<LoadLabel <LoadLabel
ready={this.state.users !== -1} ready={this.state.users !== -1}
value={this.state.users} /> value={this.state.users}
</dd> />
<dt> </dd>
Groups <dt>
</dt> Groups
<dd> </dt>
<LoadLabel <dd>
ready={this.state.groups !== -1} <LoadLabel
value={this.state.groups} /> ready={this.state.groups !== -1}
</dd> value={this.state.groups}
<dt> />
Computers </dd>
</dt> <dt>
<dd> Computers
<LoadLabel </dt>
ready={this.state.computers !== -1} <dd>
value={this.state.computers} /> <LoadLabel
</dd> ready={this.state.computers !== -1}
<br /> value={this.state.computers}
<dt> />
Foreign Users </dd>
</dt> <br />
<dd> <dt>
<NodeALink Foreign Users
ready={this.state.foreignUsers !== -1} </dt>
value={this.state.foreignUsers} <dd>
click={function(){ <NodeALink
emitter.emit('query', "MATCH (a:User) WHERE NOT a.name ENDS WITH ('@' + {domain}) WITH a MATCH (b:Group) WHERE b.name ENDS WITH ('@' + {domain}) WITH a,b MATCH (a)-[r:MemberOf]-(b) RETURN a,r,b", {domain: this.state.label}) ready={this.state.foreignUsers !== -1}
}.bind(this)} /> value={this.state.foreignUsers}
</dd> click={function(){
<dt> emitter.emit('query', "MATCH (a:User) WHERE NOT a.name ENDS WITH ('@' + {domain}) WITH a MATCH (b:Group) WHERE b.name ENDS WITH ('@' + {domain}) WITH a,b MATCH (a)-[r:MemberOf]-(b) RETURN a,r,b", {domain: this.state.label});
Foreign Groups }.bind(this)}
</dt> />
<dd> </dd>
<NodeALink <dt>
ready={this.state.foreignGroups !== -1} Foreign Groups
value={this.state.foreignGroups} </dt>
click={function(){ <dd>
emitter.emit('query', "MATCH (a:Group) WHERE NOT a.name ENDS WITH ('@' + {domain}) WITH a MATCH (b:Group) WHERE b.name ENDS WITH ('@' + {domain}) WITH a,b MATCH (a)-[r:MemberOf]-(b) RETURN a,r,b", {domain: this.state.label}) <NodeALink
}.bind(this)} /> ready={this.state.foreignGroups !== -1}
</dd> value={this.state.foreignGroups}
<dt> click={function(){
Foreign Admins emitter.emit('query', "MATCH (a:Group) WHERE NOT a.name ENDS WITH ('@' + {domain}) WITH a MATCH (b:Group) WHERE b.name ENDS WITH ('@' + {domain}) WITH a,b MATCH (a)-[r:MemberOf]-(b) RETURN a,r,b", {domain: this.state.label});
</dt> }.bind(this)}
<dd> />
<NodeALink </dd>
ready={this.state.foreignAdmins !== -1} <dt>
value={this.state.foreignAdmins} Foreign Admins
click={function(){ </dt>
emitter.emit('query', "MATCH (a:Group) WHERE NOT a.name ENDS WITH ('@' + {domain}) WITH a MATCH (b:Group) WHERE b.name ENDS WITH ('@' + {domain}) WITH a,b MATCH (a)-[r:MemberOf]-(b) RETURN a,r,b", {domain: this.state.label}) <dd>
}.bind(this)} /> <NodeALink
</dd> ready={this.state.foreignAdmins !== -1}
<br /> value={this.state.foreignAdmins}
<dt> click={function(){
Inbound Trusts emitter.emit('query', "MATCH (a:Group) WHERE NOT a.name ENDS WITH ('@' + {domain}) WITH a MATCH (b:Group) WHERE b.name ENDS WITH ('@' + {domain}) WITH a,b MATCH (a)-[r:MemberOf]-(b) RETURN a,r,b", {domain: this.state.label});
</dt> }.bind(this)}
<dd> />
<NodeALink </dd>
ready={this.state.firstDegreeInboundTrusts !== -1} <br />
value={this.state.firstDegreeInboundTrusts} <dt>
click={function(){ Inbound Trusts
emitter.emit('query', "MATCH (a:Domain {name:{domain}})<-[r:TrustedBy]-(b:Domain) RETURN a,r,b", {domain: this.state.label}) </dt>
}.bind(this)} /> <dd>
</dd> <NodeALink
<dt> ready={this.state.firstDegreeInboundTrusts !== -1}
Effective Inbound Trusts value={this.state.firstDegreeInboundTrusts}
</dt> click={function(){
<dd> emitter.emit('query', "MATCH (a:Domain {name:{domain}})<-[r:TrustedBy]-(b:Domain) RETURN a,r,b", {domain: this.state.label});
<NodeALink }.bind(this)}
ready={this.state.effectiveInboundTrusts !== -1} />
value={this.state.effectiveInboundTrusts} </dd>
click={function(){ <dt>
emitter.emit('query', "MATCH p=shortestPath((a:Domain {name:{domain}})<-[r:TrustedBy*1..]-(b:Domain)) RETURN p", {domain: this.state.label}) Effective Inbound Trusts
}.bind(this)} /> </dt>
</dd> <dd>
<dt> <NodeALink
Outbound Trusts ready={this.state.effectiveInboundTrusts !== -1}
</dt> value={this.state.effectiveInboundTrusts}
<dd> click={function(){
<NodeALink emitter.emit('query', "MATCH (b:Domain) WHERE NOT b.name={domain} WITH b MATCH p=shortestPath((a:Domain {name:{domain}})<-[r:TrustedBy*1..]-(b)) RETURN p", {domain: this.state.label});
ready={this.state.firstDegreeOutboundTrusts !== -1} }.bind(this)}
value={this.state.firstDegreeOutboundTrusts} />
click={function(){ </dd>
emitter.emit('query', "MATCH (a:Domain {name:{domain}})-[r:TrustedBy]->(b:Domain) RETURN a,r,b", {domain: this.state.label}) <dt>
}.bind(this)} /> Outbound Trusts
</dd> </dt>
<dt> <dd>
Effective Outbound Trusts <NodeALink
</dt> ready={this.state.firstDegreeOutboundTrusts !== -1}
<dd> value={this.state.firstDegreeOutboundTrusts}
<NodeALink click={function(){
ready={this.state.effectiveOutboundTrusts !== -1} emitter.emit('query', "MATCH (a:Domain {name:{domain}})-[r:TrustedBy]->(b:Domain) RETURN a,r,b", {domain: this.state.label});
value={this.state.effectiveOutboundTrusts} }.bind(this)}
click={function(){ />
emitter.emit('query', "MATCH p=shortestPath((a:Domain {name:{domain}})-[r:TrustedBy*1..]->(b:Domain)) RETURN p", {domain: this.state.label}) </dd>
}.bind(this)} /> <dt>
</dd> Effective Outbound Trusts
</dl> </dt>
</div> <dd>
); <NodeALink
} ready={this.state.effectiveOutboundTrusts !== -1}
value={this.state.effectiveOutboundTrusts}
click={function(){
emitter.emit('query', "MATCH (b:Domain) WHERE NOT b.name={domain} WITH b MATCH p=shortestPath((a:Domain {name:{domain}})-[r:TrustedBy*1..]->(b:Domain)) RETURN p", {domain: this.state.label});
}.bind(this)}
/>
</dd>
</dl>
</div>
);
}
} }
DomainNodeData.propTypes = { DomainNodeData.propTypes = {
visible : React.PropTypes.bool.isRequired visible : React.PropTypes.bool.isRequired
} };

View File

@ -1,394 +1,405 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import NodeALink from './NodeALink' import NodeALink from './NodeALink';
import PropTypes from 'prop-types' import PropTypes from 'prop-types';
export default class GroupNodeData extends Component { export default class GroupNodeData extends Component {
constructor(){ constructor(){
super(); super();
this.state = { this.state = {
label: "", label: "",
directMembers: -1, directMembers: -1,
unrolledMembers: -1, unrolledMembers: -1,
directAdminTo: -1, directAdminTo: -1,
derivativeAdminTo: -1, derivativeAdminTo: -1,
unrolledMemberOf: -1, unrolledMemberOf: -1,
sessions: -1, sessions: -1,
foreignGroupMembership: -1, foreignGroupMembership: -1,
foreignGroupMembers: -1, foreignGroupMembers: -1,
firstDegreeGroupMembership: -1, firstDegreeGroupMembership: -1,
groupDelegatedAdmin: -1, groupDelegatedAdmin: -1,
firstdegreeControl: -1, firstdegreeControl: -1,
groupDelegatedControl: -1, groupDelegatedControl: -1,
transitiveControl: -1, transitiveControl: -1,
firstDegreeControllers: -1, firstDegreeControllers: -1,
unrolledControllers: -1, unrolledControllers: -1,
transitiveControllers: -1, transitiveControllers: -1,
driversessions: [] driversessions: []
} };
emitter.on('groupNodeClicked', this.getNodeData.bind(this)); emitter.on('groupNodeClicked', this.getNodeData.bind(this));
} }
getNodeData(payload){ getNodeData(payload){
$.each(this.state.driversessions, function(index, record){ $.each(this.state.driversessions, function(index, record){
record.close(); record.close();
}) });
this.setState({ this.setState({
label: payload, label: payload,
directMembers: -1, directMembers: -1,
unrolledMembers: -1, unrolledMembers: -1,
directAdminTo: -1, directAdminTo: -1,
derivativeAdminTo: -1, derivativeAdminTo: -1,
unrolledMemberOf: -1, unrolledMemberOf: -1,
sessions: -1, sessions: -1,
foreignGroupMembership: -1, foreignGroupMembership: -1,
foreignGroupMembers: -1, foreignGroupMembers: -1,
firstDegreeGroupMembership: -1, firstDegreeGroupMembership: -1,
groupDelegatedAdmin: -1, groupDelegatedAdmin: -1,
firstdegreeControl: -1, firstdegreeControl: -1,
groupDelegatedControl: -1, groupDelegatedControl: -1,
transitiveControl: -1, transitiveControl: -1,
firstDegreeControllers: -1, firstDegreeControllers: -1,
unrolledControllers: -1, unrolledControllers: -1,
transitiveControllers: -1 transitiveControllers: -1
}) });
var domain = '@' + payload.split('@').last() var domain = '@' + payload.split('@').last();
var s1 = driver.session() var s1 = driver.session();
var s2 = driver.session() var s2 = driver.session();
var s3 = driver.session() var s3 = driver.session();
var s4 = driver.session() var s4 = driver.session();
var s5 = driver.session() var s5 = driver.session();
var s6 = driver.session() var s6 = driver.session();
var s7 = driver.session() var s7 = driver.session();
var s8 = driver.session() var s8 = driver.session();
var s9 = driver.session() var s9 = driver.session();
var s10 = driver.session() var s10 = driver.session();
var s11 = driver.session() var s11 = driver.session();
var s12 = driver.session() var s12 = driver.session();
var s13 = driver.session() var s13 = driver.session();
var s14 = driver.session() var s14 = driver.session();
var s15 = driver.session() var s15 = driver.session();
var s16 = driver.session() var s16 = driver.session();
s1.run("MATCH (a)-[b:MemberOf]->(c:Group {name:{name}}) RETURN count(a)", {name:payload}) s1.run("MATCH (a)-[b:MemberOf]->(c:Group {name:{name}}) RETURN count(a)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'directMembers':result.records[0]._fields[0].low}) this.setState({'directMembers':result.records[0]._fields[0].low});
s1.close() s1.close();
}.bind(this)) }.bind(this));
s2.run("MATCH p = (n)-[r:MemberOf*1..]->(g:Group {name:{name}}) RETURN COUNT(n)", {name:payload}) s2.run("MATCH p = (n)-[r:MemberOf*1..]->(g:Group {name:{name}}) RETURN COUNT(n)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'unrolledMembers':result.records[0]._fields[0].low}) this.setState({'unrolledMembers':result.records[0]._fields[0].low});
s2.close() s2.close();
}.bind(this)) }.bind(this));
s3.run("MATCH (n:Group {name:{name}})-[r:AdminTo]->(m:Computer) RETURN count(distinct(m))", {name:payload}) s3.run("MATCH (n:Group {name:{name}})-[r:AdminTo]->(m:Computer) RETURN count(distinct(m))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'directAdminTo':result.records[0]._fields[0].low}) this.setState({'directAdminTo':result.records[0]._fields[0].low});
s3.close() s3.close();
}.bind(this)) }.bind(this));
s4.run("MATCH p = shortestPath((g:Group {name:{name}})-[r:MemberOf|AdminTo|HasSession*1..]->(c:Computer)) RETURN COUNT(DISTINCT(c))", {name:payload}) s4.run("MATCH (c:Computer) WHERE NOT c.name={name} WITH c MATCH p = shortestPath((g:Group {name:{name}})-[r:MemberOf|AdminTo|HasSession*1..]->(c)) RETURN COUNT(DISTINCT(c))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'derivativeAdminTo':result.records[0]._fields[0].low}) this.setState({'derivativeAdminTo':result.records[0]._fields[0].low});
s4.close() s4.close();
}.bind(this)) }.bind(this));
s5.run("MATCH p = (g1:Group {name:{name}})-[r:MemberOf*1..]->(g2:Group) RETURN COUNT(DISTINCT(g2))", {name:payload}) s5.run("MATCH p = (g1:Group {name:{name}})-[r:MemberOf*1..]->(g2:Group) RETURN COUNT(DISTINCT(g2))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'unrolledMemberOf':result.records[0]._fields[0].low}) this.setState({'unrolledMemberOf':result.records[0]._fields[0].low});
s5.close() s5.close();
}.bind(this)) }.bind(this));
s6.run("MATCH p = (c:Computer)-[r1:HasSession]->(u:User)-[r2:MemberOf*1..]->(g:Group {name: {name}}) RETURN COUNT(r1)", {name:payload}) s6.run("MATCH p = (c:Computer)-[r1:HasSession]->(u:User)-[r2:MemberOf*1..]->(g:Group {name: {name}}) RETURN COUNT(r1)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'sessions':result.records[0]._fields[0].low}) this.setState({'sessions':result.records[0]._fields[0].low});
s6.close() s6.close();
}.bind(this)) }.bind(this));
s7.run("MATCH (n:Group) WHERE NOT n.name ENDS WITH {domain} WITH n MATCH (m:Group {name:{name}}) MATCH (m)-[r:MemberOf]->(n) RETURN count(n)", {name:payload, domain:domain}) s7.run("MATCH (n:Group) WHERE NOT n.name ENDS WITH {domain} WITH n MATCH (m:Group {name:{name}}) MATCH (m)-[r:MemberOf]->(n) RETURN count(n)", {name:payload, domain:domain})
.then(function(result){ .then(function(result){
this.setState({'foreignGroupMembership':result.records[0]._fields[0].low}) this.setState({'foreignGroupMembership':result.records[0]._fields[0].low});
s7.close() s7.close();
}.bind(this)) }.bind(this));
s8.run("MATCH p = (n)-[r:MemberOf*1..]->(g:Group {name:{name}}) WHERE NOT g.domain = n.domain RETURN COUNT(DISTINCT(n))", {name:payload}) s8.run("MATCH p = (n)-[r:MemberOf*1..]->(g:Group {name:{name}}) WHERE NOT g.domain = n.domain RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'foreignGroupMembers':result.records[0]._fields[0].low}) this.setState({'foreignGroupMembers':result.records[0]._fields[0].low});
s8.close() s8.close();
}.bind(this)) }.bind(this));
s9.run("MATCH p = (g1:Group {name:{name}})-[r:MemberOf]->(g2:Group) RETURN COUNT(DISTINCT(g2))", {name:payload}) s9.run("MATCH p = (g1:Group {name:{name}})-[r:MemberOf]->(g2:Group) RETURN COUNT(DISTINCT(g2))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'firstDegreeGroupMembership':result.records[0]._fields[0].low}) this.setState({'firstDegreeGroupMembership':result.records[0]._fields[0].low});
s9.close() s9.close();
}.bind(this)) }.bind(this));
s10.run("MATCH p = (g1:Group {name:{name}})-[r1:MemberOf*1..]->(g2:Group)-[r2:AdminTo]->(c:Computer) RETURN COUNT(DISTINCT(c))", {name:payload}) s10.run("MATCH p = (g1:Group {name:{name}})-[r1:MemberOf*1..]->(g2:Group)-[r2:AdminTo]->(c:Computer) RETURN COUNT(DISTINCT(c))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'groupDelegatedAdmin':result.records[0]._fields[0].low}) this.setState({'groupDelegatedAdmin':result.records[0]._fields[0].low});
s10.close() s10.close();
}.bind(this)) }.bind(this));
s11.run("MATCH p = (g:Group {name:{name}})-[r:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN COUNT(DISTINCT(n))", {name:payload}) s11.run("MATCH p = (g:Group {name:{name}})-[r:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'firstdegreeControl':result.records[0]._fields[0].low}) this.setState({'firstdegreeControl':result.records[0]._fields[0].low});
s11.close() s11.close();
}.bind(this)) }.bind(this));
s12.run("MATCH p = (g1:Group {name:{name}})-[r1:MemberOf*1..]->(g2:Group)-[r2:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN COUNT(DISTINCT(n))", {name:payload}) s12.run("MATCH p = (g1:Group {name:{name}})-[r1:MemberOf*1..]->(g2:Group)-[r2:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'groupDelegatedControl':result.records[0]._fields[0].low}) this.setState({'groupDelegatedControl':result.records[0]._fields[0].low});
s12.close() s12.close();
}.bind(this)) }.bind(this));
s13.run("MATCH p = shortestPath((g:Group {name:{name}})-[r:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) RETURN COUNT(DISTINCT(n))", {name:payload}) s13.run("MATCH (n) WHERE NOT n.name={name} WITH n MATCH p = shortestPath((g:Group {name:{name}})-[r:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'transitiveControl':result.records[0]._fields[0].low}) this.setState({'transitiveControl':result.records[0]._fields[0].low});
s13.close() s13.close();
}.bind(this)) }.bind(this));
s14.run("MATCH p = (n)-[r:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(g:Group {name:{name}}) RETURN COUNT(DISTINCT(n))", {name:payload}) s14.run("MATCH p = (n)-[r:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(g:Group {name:{name}}) RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'firstDegreeControllers':result.records[0]._fields[0].low}) this.setState({'firstDegreeControllers':result.records[0]._fields[0].low});
s14.close() s14.close();
}.bind(this)) }.bind(this));
s15.run("MATCH p = (n1)-[r:MemberOf*1..]->(g1:Group)-[r1:AddMembers|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(g2:Group {name: {name}}) WITH LENGTH(p) as pathLength, p, n1 WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.name = g2.name) AND NOT n1.name = g2.name RETURN COUNT(DISTINCT(n1))", {name:payload}) s15.run("MATCH p = (n1)-[r:MemberOf*1..]->(g1:Group)-[r1:AddMembers|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(g2:Group {name: {name}}) WITH LENGTH(p) as pathLength, p, n1 WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.name = g2.name) AND NOT n1.name = g2.name RETURN COUNT(DISTINCT(n1))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'unrolledControllers':result.records[0]._fields[0].low}) this.setState({'unrolledControllers':result.records[0]._fields[0].low});
s15.close() s15.close();
}.bind(this)) }.bind(this));
s16.run("MATCH p = shortestPath((n)-[r:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(g:Group {name:{name}})) RETURN COUNT(DISTINCT(n))", {name:payload}) s16.run("MATCH (n) WHERE NOT n.name={name} WITH n MATCH p = shortestPath((n)-[r:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(g:Group {name:{name}})) RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'transitiveControllers':result.records[0]._fields[0].low}) this.setState({'transitiveControllers':result.records[0]._fields[0].low});
s16.close() s16.close();
}.bind(this)) }.bind(this));
this.setState({'driversessions': [s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15,s16]}) this.setState({'driversessions': [s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15,s16]});
} }
render() { render() {
var domain = '@' + this.state.label.split('@') var domain = '@' + this.state.label.split('@');
return ( return (
<div className={this.props.visible ? "" : "displaynone"}> <div className={this.props.visible ? "" : "displaynone"}>
<dl className='dl-horizontal'> <dl className='dl-horizontal'>
<h4>Node Info</h4> <h4>Node Info</h4>
<dt> <dt>
Name Name
</dt> </dt>
<dd> <dd>
{this.state.label} {this.state.label}
</dd> </dd>
<dt> <dt>
Sessions Sessions
</dt> </dt>
<dd> <dd>
<NodeALink <NodeALink
ready={this.state.sessions !== -1} ready={this.state.sessions !== -1}
value={this.state.sessions} value={this.state.sessions}
click={function(){ click={function(){
emitter.emit('query', "MATCH p = (c:Computer)-[r1:HasSession]->(u:User)-[r2:MemberOf*1..]->(g:Group {name: {name}}) RETURN p", {name: this.state.label}, emitter.emit('query', "MATCH p = (c:Computer)-[r1:HasSession]->(u:User)-[r2:MemberOf*1..]->(g:Group {name: {name}}) RETURN p", {name: this.state.label},
"",this.state.label) "",this.state.label);
}.bind(this)} /> }.bind(this)}
</dd> />
<br /> </dd>
<h4>Group Members</h4> <h4>Group Members</h4>
<dt> <dt>
Direct Members Direct Members
</dt> </dt>
<dd> <dd>
<NodeALink <NodeALink
ready={this.state.directMembers !== -1} ready={this.state.directMembers !== -1}
value={this.state.directMembers} value={this.state.directMembers}
click={function(){ click={function(){
emitter.emit('query', "MATCH (n)-[r:MemberOf]->(m:Group {name:{name}}) RETURN n,r,m", {name: this.state.label}) emitter.emit('query', "MATCH (n)-[r:MemberOf]->(m:Group {name:{name}}) RETURN n,r,m", {name: this.state.label});
}.bind(this)} /> }.bind(this)}
</dd> />
<dt> </dd>
Unrolled Members <dt>
</dt> Unrolled Members
<dd> </dt>
<NodeALink <dd>
ready={this.state.unrolledMembers !== -1} <NodeALink
value={this.state.unrolledMembers} ready={this.state.unrolledMembers !== -1}
click={function(){ value={this.state.unrolledMembers}
emitter.emit('query', "MATCH p = (n)-[r:MemberOf*1..]->(g:Group {name:{name}}) RETURN p", {name: this.state.label}, click={function(){
this.state.label) emitter.emit('query', "MATCH p = (n)-[r:MemberOf*1..]->(g:Group {name:{name}}) RETURN p", {name: this.state.label},
}.bind(this)} /> this.state.label);
</dd> }.bind(this)}
<dt> />
Foreign Members </dd>
</dt> <dt>
<dd> Foreign Members
<NodeALink </dt>
ready={this.state.foreignGroupMembers !== -1} <dd>
value={this.state.foreignGroupMembers} <NodeALink
click={function(){ ready={this.state.foreignGroupMembers !== -1}
emitter.emit('query', "MATCH p = (n)-[r:MemberOf*1..]->(g:Group {name:{name}}) WHERE NOT g.domain = n.domain RETURN p", {name: this.state.label}, value={this.state.foreignGroupMembers}
this.state.label) click={function(){
}.bind(this)} /> emitter.emit('query', "MATCH p = (n)-[r:MemberOf*1..]->(g:Group {name:{name}}) WHERE NOT g.domain = n.domain RETURN p", {name: this.state.label},
</dd> this.state.label);
<br /> }.bind(this)}
<h4>Group Membership</h4> />
<dt> </dd>
First Degree Group Membership <h4>Group Membership</h4>
</dt> <dt>
<dd> First Degree Group Membership
<NodeALink </dt>
ready={this.state.firstDegreeGroupMembership !== -1} <dd>
value={this.state.firstDegreeGroupMembership} <NodeALink
click={function(){ ready={this.state.firstDegreeGroupMembership !== -1}
emitter.emit('query', "MATCH p = (g1:Group {name:{name}})-[r:MemberOf]->(g2:Group) RETURN p", {name: this.state.label}, value={this.state.firstDegreeGroupMembership}
this.state.label) click={function(){
}.bind(this)} /> emitter.emit('query', "MATCH p = (g1:Group {name:{name}})-[r:MemberOf]->(g2:Group) RETURN p", {name: this.state.label},
</dd> this.state.label);
<dt> }.bind(this)}
Unrolled Member Of />
</dt> </dd>
<dd> <dt>
<NodeALink Unrolled Member Of
ready={this.state.unrolledMemberOf !== -1} </dt>
value={this.state.unrolledMemberOf} <dd>
click={function(){ <NodeALink
emitter.emit('query', "MATCH p = (g1:Group {name:{name}})-[r:MemberOf*1..]->(g2:Group) RETURN p", {name: this.state.label}, ready={this.state.unrolledMemberOf !== -1}
this.state.label) value={this.state.unrolledMemberOf}
}.bind(this)} /> click={function(){
</dd> emitter.emit('query', "MATCH p = (g1:Group {name:{name}})-[r:MemberOf*1..]->(g2:Group) RETURN p", {name: this.state.label},
<dt> this.state.label);
Foreign Group Membership }.bind(this)}
</dt> />
<dd> </dd>
<NodeALink <dt>
ready={this.state.foreignGroupMembership !== -1} Foreign Group Membership
value={this.state.foreignGroupMembership} </dt>
click={function(){ <dd>
emitter.emit('query', "MATCH (n:Group) WHERE NOT n.name ENDS WITH {domain} WITH n MATCH (m:Group {name:{name}}) MATCH (m)-[r:MemberOf]->(n) RETURN m,r,n", {name: this.state.label, domain: domain}) <NodeALink
}.bind(this)} /> ready={this.state.foreignGroupMembership !== -1}
</dd> value={this.state.foreignGroupMembership}
<br /> click={function(){
<h4>Local Admin Rights</h4> emitter.emit('query', "MATCH (n:Group) WHERE NOT n.name ENDS WITH {domain} WITH n MATCH (m:Group {name:{name}}) MATCH (m)-[r:MemberOf]->(n) RETURN m,r,n", {name: this.state.label, domain: domain});
<dt> }.bind(this)}
First Degree Local Admin />
</dt> </dd>
<dd> <h4>Local Admin Rights</h4>
<NodeALink <dt>
ready={this.state.directAdminTo !== -1} First Degree Local Admin
value={this.state.directAdminTo} </dt>
click={function(){ <dd>
emitter.emit('query', "MATCH p=(g:Group {name:{name}})-[r:AdminTo]->(c:Computer) RETURN p", {name: this.state.label}, <NodeALink
this.state.label) ready={this.state.directAdminTo !== -1}
}.bind(this)} /> value={this.state.directAdminTo}
</dd> click={function(){
<dt> emitter.emit('query', "MATCH p=(g:Group {name:{name}})-[r:AdminTo]->(c:Computer) RETURN p", {name: this.state.label},
Group Delegated Local Admin Rights this.state.label);
</dt> }.bind(this)}
<dd> />
<NodeALink </dd>
ready={this.state.groupDelegatedAdmin !== -1} <dt>
value={this.state.groupDelegatedAdmin} Group Delegated Local Admin Rights
click={function(){ </dt>
emitter.emit('query', "MATCH p = (g1:Group {name:{name}})-[r1:MemberOf*1..]->(g2:Group)-[r2:AdminTo]->(c:Computer) RETURN p", {name: this.state.label}, <dd>
this.state.label) <NodeALink
}.bind(this)} /> ready={this.state.groupDelegatedAdmin !== -1}
</dd> value={this.state.groupDelegatedAdmin}
<dt> click={function(){
Derivative Local Admin Rights emitter.emit('query', "MATCH p = (g1:Group {name:{name}})-[r1:MemberOf*1..]->(g2:Group)-[r2:AdminTo]->(c:Computer) RETURN p", {name: this.state.label},
</dt> this.state.label);
<dd> }.bind(this)}
<NodeALink />
ready={this.state.derivativeAdminTo !== -1} </dd>
value={this.state.derivativeAdminTo} <dt>
click={function(){ Derivative Local Admin Rights
emitter.emit('query', "MATCH p = shortestPath((g:Group {name:{name}})-[r:MemberOf|AdminTo|HasSession*1..]->(c:Computer)) RETURN p", {name: this.state.label}, </dt>
this.state.label) <dd>
}.bind(this)} /> <NodeALink
</dd> ready={this.state.derivativeAdminTo !== -1}
<br /> value={this.state.derivativeAdminTo}
<h4>Outbound Object Control</h4> click={function(){
<dt> emitter.emit('query', "MATCH (c:Computer) WHERE NOT c.name={name} WITH c MATCH p = shortestPath((g:Group {name:{name}})-[r:MemberOf|AdminTo|HasSession*1..]->(c)) RETURN p", {name: this.state.label},
First Degree Object Control this.state.label);
</dt> }.bind(this)}
<dd> />
<NodeALink </dd>
ready={this.state.firstdegreeControl !== -1} <h4>Outbound Object Control</h4>
value={this.state.firstdegreeControl} <dt>
click={function(){ First Degree Object Control
emitter.emit('query', "MATCH p = (g:Group {name:{name}})-[r1:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN p", {name:this.state.label}) </dt>
}.bind(this)} /> <dd>
</dd> <NodeALink
<dt> ready={this.state.firstdegreeControl !== -1}
Group Delegated Object Control value={this.state.firstdegreeControl}
</dt> click={function(){
<dd> emitter.emit('query', "MATCH p = (g:Group {name:{name}})-[r1:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN p", {name:this.state.label});
<NodeALink }.bind(this)}
ready={this.state.groupDelegatedControl !== -1} />
value={this.state.groupDelegatedControl} </dd>
click={function(){ <dt>
emitter.emit('query', "MATCH p = (g1:Group {name:{name}})-[r1:MemberOf*1..]->(g2:Group)-[r2:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN p", {name:this.state.label} Group Delegated Object Control
,this.state.label) </dt>
}.bind(this)} /> <dd>
</dd> <NodeALink
<dt> ready={this.state.groupDelegatedControl !== -1}
Transitive Object Control value={this.state.groupDelegatedControl}
</dt> click={function(){
<dd> emitter.emit('query', "MATCH p = (g1:Group {name:{name}})-[r1:MemberOf*1..]->(g2:Group)-[r2:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN p", {name:this.state.label}
<NodeALink ,this.state.label);
ready={this.state.transitiveControl !== -1} }.bind(this)}
value={this.state.transitiveControl} />
click={function(){ </dd>
emitter.emit('query', "MATCH p = shortestPath((g:Group {name:{name}})-[r1:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) RETURN p", {name:this.state.label} <dt>
,this.state.label) Transitive Object Control
}.bind(this)} /> </dt>
</dd> <dd>
<br /> <NodeALink
<h4>Inbound Object Control</h4> ready={this.state.transitiveControl !== -1}
<dt> value={this.state.transitiveControl}
Explicit Object Controllers click={function(){
</dt> emitter.emit('query', "MATCH (n) WHERE NOT n.name={name} WITH n MATCH p = shortestPath((g:Group {name:{name}})-[r1:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) RETURN p", {name:this.state.label}
<dd> ,this.state.label);
<NodeALink }.bind(this)}
ready={this.state.firstDegreeControllers !== -1} />
value={this.state.firstDegreeControllers} </dd>
click={function(){ <h4>Inbound Object Control</h4>
emitter.emit('query', "MATCH p = (n)-[r:AddMembers|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(g:Group {name: {name}}) RETURN p", {name:this.state.label} <dt>
,this.state.label) Explicit Object Controllers
}.bind(this)} /> </dt>
</dd> <dd>
<dt> <NodeALink
Unrolled Object Controllers ready={this.state.firstDegreeControllers !== -1}
</dt> value={this.state.firstDegreeControllers}
<dd> click={function(){
<NodeALink emitter.emit('query', "MATCH p = (n)-[r:AddMembers|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(g:Group {name: {name}}) RETURN p", {name:this.state.label}
ready={this.state.unrolledControllers !== -1} ,this.state.label);
value={this.state.unrolledControllers} }.bind(this)}
click={function(){ />
emitter.emit('query', "MATCH p = (n1)-[r:MemberOf*1..]->(g1:Group)-[r1:AddMembers|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(g2:Group {name: {name}}) WITH LENGTH(p) as pathLength, p, n1 WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.name = g2.name) AND NOT n1.name = g2.name RETURN p", {name:this.state.label} </dd>
,this.state.label) <dt>
}.bind(this)} /> Unrolled Object Controllers
</dd> </dt>
<dt> <dd>
Transitive Object Controllers <NodeALink
</dt> ready={this.state.unrolledControllers !== -1}
<dd> value={this.state.unrolledControllers}
<NodeALink click={function(){
ready={this.state.transitiveControllers !== -1} emitter.emit('query', "MATCH p = (n1)-[r:MemberOf*1..]->(g1:Group)-[r1:AddMembers|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(g2:Group {name: {name}}) WITH LENGTH(p) as pathLength, p, n1 WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.name = g2.name) AND NOT n1.name = g2.name RETURN p", {name:this.state.label}
value={this.state.transitiveControllers} ,this.state.label);
click={function(){ }.bind(this)}
emitter.emit('query', "MATCH p = shortestPath((n)-[r:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(g:Group {name: {name}})) RETURN p", {name:this.state.label} />
,this.state.label) </dd>
}.bind(this)} /> <dt>
</dd> Transitive Object Controllers
</dl> </dt>
</div> <dd>
); <NodeALink
} ready={this.state.transitiveControllers !== -1}
value={this.state.transitiveControllers}
click={function(){
emitter.emit('query', "MATCH (n) WHERE NOT n.name={name} WITH n MATCH p = shortestPath((n)-[r:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(g:Group {name: {name}})) RETURN p", {name:this.state.label}
,this.state.label);
}.bind(this)}
/>
</dd>
</dl>
</div>
);
}
} }
GroupNodeData.propTypes = { GroupNodeData.propTypes = {
visible : React.PropTypes.bool.isRequired visible : React.PropTypes.bool.isRequired
} };

View File

@ -0,0 +1,51 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class NodePropItem extends Component{
constructor(props){
super(props);
}
isArray(object){
return object && typeof object === 'object' && object.constructor === Array;
}
render() {
var val;
var obj = this.props.keyValue;
if (obj.hasOwnProperty('low')){
return [
<dt>{this.props.keyName}</dt>,
<dd>{obj.low}</dd>
];
}else if (this.isArray(obj)){
console.log(obj);
if (obj.length === 0){
return [
<dt>{this.props.keyName}</dt>,
<dd>None</dd>
];
}else{
var elements = [];
$.each(obj, function(index, prop){
elements.push(<dt></dt>);
elements.push(<dd>{prop}</dd>);
});
elements[0] = <dt>Service Principal Names</dt>;
}
return elements;
}else if (typeof obj === 'boolean'){
return [
<dt>{this.props.keyName}</dt>,
<dd>{this.props.keyValue.toString().toTitleCase()}</dd>
];
}else{
return [
<dt>{this.props.keyName}</dt>,
<dd>{this.props.keyValue.toString()}</dd>
];
}
}
}

View File

@ -1,363 +1,432 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import NodeALink from './NodeALink' import NodeALink from './NodeALink';
import PropTypes from 'prop-types' import NodePropItem from './NodePropItem';
import PropTypes from 'prop-types';
import { If, Then, Else } from 'react-if';
export default class UserNodeData extends Component { export default class UserNodeData extends Component {
constructor(){ constructor(){
super(); super();
this.state = { this.state = {
label: "", label: "",
samAccountName: "None", firstDegreeGroupMembership: -1,
displayName: "None", unrolledGroupMembership: -1,
pwdLastChanged: "None", foreignGroupMembership: -1,
firstDegreeGroupMembership: -1, firstDegreeLocalAdmin: -1,
unrolledGroupMembership: -1, groupDelegatedLocalAdmin: -1,
foreignGroupMembership: -1, derivativeLocalAdmin: -1,
firstDegreeLocalAdmin: -1, sessions: -1,
groupDelegatedLocalAdmin: -1, firstdegreeControllers: -1,
derivativeLocalAdmin: -1, unrolledControllers: -1,
sessions: -1, transitiveControllers: -1,
firstdegreeControllers: -1, firstdegreeControl: -1,
unrolledControllers: -1, unrolledControl: -1,
transitiveControllers: -1, transitiveControl: -1,
firstdegreeControl: -1, driversessions : [],
unrolledControl: -1, propertyMap: {ServicePrincipalNames: []}
transitiveControl: -1, };
driversessions : []
}
emitter.on('userNodeClicked', this.getNodeData.bind(this)); emitter.on('userNodeClicked', this.getNodeData.bind(this));
} }
getNodeData(payload){ getNodeData(payload){
$.each(this.state.driversessions,function(index, record){ $.each(this.state.driversessions,function(index, record){
record.close(); record.close();
}) });
this.setState({ this.setState({
label: payload, label: payload,
samAccountName: "None", firstDegreeGroupMembership: -1,
displayName: "None", unrolledGroupMembership: -1,
pwdLastChanged: "None", foreignGroupMembership: -1,
firstDegreeGroupMembership: -1, firstDegreeLocalAdmin: -1,
unrolledGroupMembership: -1, groupDelegatedLocalAdmin: -1,
foreignGroupMembership: -1, derivativeLocalAdmin: -1,
firstDegreeLocalAdmin: -1, sessions: -1,
groupDelegatedLocalAdmin: -1, firstdegreeControllers: -1,
derivativeLocalAdmin: -1, unrolledControllers: -1,
sessions: -1, transitiveControllers: -1,
firstdegreeControllers: -1, firstdegreeControl: -1,
unrolledControllers: -1, unrolledControl: -1,
transitiveControllers: -1, transitiveControl: -1,
firstdegreeControl: -1, propertyMap: {ServicePrincipalNames: []}
unrolledControl: -1, });
transitiveControl: -1
})
var domain = '@' + payload.split('@').last() var domain = '@' + payload.split('@').last();
var s1 = driver.session() var s1 = driver.session();
var s2 = driver.session() var s2 = driver.session();
var s3 = driver.session() var s3 = driver.session();
var s4 = driver.session() var s4 = driver.session();
var s5 = driver.session() var s5 = driver.session();
var s6 = driver.session() var s6 = driver.session();
var s7 = driver.session() var s7 = driver.session();
var s8 = driver.session() var s8 = driver.session();
var s9 = driver.session() var s9 = driver.session();
var s10 = driver.session() var s10 = driver.session();
var s11 = driver.session() var s11 = driver.session();
var s12 = driver.session() var s12 = driver.session();
var s13 = driver.session() var s13 = driver.session();
s1.run("MATCH (n:Group) WHERE NOT n.name ENDS WITH {domain} WITH n MATCH (m:User {name:{name}}) MATCH (m)-[r:MemberOf*1..]->(n) RETURN count(n)", {name:payload, domain: domain}) var props = driver.session();
.then(function(result){ props.run("MATCH (n:User {name:{name}}) RETURN n", {name: payload})
this.setState({'foreignGroupMembership':result.records[0]._fields[0].low}) .then(function(result){
s1.close() var properties = result.records[0]._fields[0].properties;
}.bind(this)) this.setState({propertyMap: properties});
props.close();
}.bind(this));
s2.run("MATCH (n:User {name:{name}}), (m:Group), p=(n)-[:MemberOf]->(m) RETURN count(m)", {name:payload}) s1.run("MATCH (n:Group) WHERE NOT n.name ENDS WITH {domain} WITH n MATCH (m:User {name:{name}}) MATCH (m)-[r:MemberOf*1..]->(n) RETURN count(n)", {name:payload, domain: domain})
.then(function(result){ .then(function(result){
this.setState({'firstDegreeGroupMembership':result.records[0]._fields[0].low}) this.setState({'foreignGroupMembership':result.records[0]._fields[0].low});
s2.close() s1.close();
}.bind(this)) }.bind(this));
s3.run("MATCH p = (n:User {name:{name}})-[r:MemberOf*1..]->(g:Group) RETURN COUNT(DISTINCT(g))", {name:payload}) s2.run("MATCH (n:User {name:{name}}), (m:Group), p=(n)-[:MemberOf]->(m) RETURN count(m)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'unrolledGroupMembership':result.records[0]._fields[0].low}) this.setState({'firstDegreeGroupMembership':result.records[0]._fields[0].low});
s3.close() s2.close();
}.bind(this)) }.bind(this));
s4.run("MATCH p = (n:User {name:{name}})-[r:AdminTo]->(c:Computer) RETURN COUNT(DISTINCT(c))", {name:payload}) s3.run("MATCH p = (n:User {name:{name}})-[r:MemberOf*1..]->(g:Group) RETURN COUNT(DISTINCT(g))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'firstDegreeLocalAdmin':result.records[0]._fields[0].low}) this.setState({'unrolledGroupMembership':result.records[0]._fields[0].low});
s4.close() s3.close();
}.bind(this)) }.bind(this));
s5.run("MATCH p=(n:User {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AdminTo]->(c:Computer) RETURN count(distinct(c))", {name:payload}) s4.run("MATCH p = (n:User {name:{name}})-[r:AdminTo]->(c:Computer) RETURN COUNT(DISTINCT(c))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'groupDelegatedLocalAdmin':result.records[0]._fields[0].low}) this.setState({'firstDegreeLocalAdmin':result.records[0]._fields[0].low});
s5.close() s4.close();
}.bind(this)) }.bind(this));
s6.run("MATCH p = shortestPath((n:User {name:{name}})-[r:HasSession|AdminTo|MemberOf*1..]->(c:Computer)) RETURN COUNT(c)", {name:payload}) s5.run("MATCH p=(n:User {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AdminTo]->(c:Computer) RETURN count(distinct(c))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'derivativeLocalAdmin':result.records[0]._fields[0].low}) this.setState({'groupDelegatedLocalAdmin':result.records[0]._fields[0].low});
s6.close() s5.close();
}.bind(this)) }.bind(this));
s7.run("MATCH p = (n:Computer)-[r:HasSession]->(m:User {name:{name}}) RETURN COUNT(DISTINCT(n))", {name:payload}) s6.run("MATCH (c:Computer) WHERE NOT c.name={name} WITH c MATCH p = shortestPath((n:User {name:{name}})-[r:HasSession|AdminTo|MemberOf*1..]->(c)) RETURN COUNT(c)", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'sessions':result.records[0]._fields[0].low}) this.setState({'derivativeLocalAdmin':result.records[0]._fields[0].low});
s7.close() s6.close();
}.bind(this)) }.bind(this));
s8.run("MATCH p = (n)-[r:AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(u1:User {name: {name}}) RETURN COUNT(DISTINCT(n))", {name:payload}) s7.run("MATCH p = (n:Computer)-[r:HasSession]->(m:User {name:{name}}) RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'firstdegreeControllers':result.records[0]._fields[0].low}) this.setState({'sessions':result.records[0]._fields[0].low});
s8.close() s7.close();
}.bind(this)) }.bind(this));
s9.run("MATCH p = (n1)-[r:MemberOf*1..]->(g:Group)-[r1:AddMembers|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(u:User {name: {name}}) WITH LENGTH(p) as pathLength, p, n1 WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.name = u.name) AND NOT n1.name = u.name RETURN COUNT(DISTINCT(n1))", {name:payload}) s8.run("MATCH p = (n)-[r:AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(u1:User {name: {name}}) RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'unrolledControllers':result.records[0]._fields[0].low}) this.setState({'firstdegreeControllers':result.records[0]._fields[0].low});
s9.close() s8.close();
}.bind(this)) }.bind(this));
s10.run("MATCH p = shortestPath((n1)-[r1:MemberOf|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(u1:User {name: {name}})) RETURN COUNT(DISTINCT(n1))", {name:payload}) s9.run("MATCH p = (n1)-[r:MemberOf*1..]->(g:Group)-[r1:AddMembers|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(u:User {name: {name}}) WITH LENGTH(p) as pathLength, p, n1 WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.name = u.name) AND NOT n1.name = u.name RETURN COUNT(DISTINCT(n1))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'transitiveControllers':result.records[0]._fields[0].low}) this.setState({'unrolledControllers':result.records[0]._fields[0].low});
s10.close() s9.close();
}.bind(this)) }.bind(this));
s11.run("MATCH p = (u:User {name:{name}})-[r1:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN COUNT(DISTINCT(n))", {name:payload}) s10.run("MATCH (n1) WHERE NOT n1.name={name} WITH n1 MATCH p = shortestPath((n1)-[r1:MemberOf|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(u1:User {name: {name}})) RETURN COUNT(DISTINCT(n1))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'firstdegreeControl':result.records[0]._fields[0].low}) this.setState({'transitiveControllers':result.records[0]._fields[0].low});
s11.close() s10.close();
}.bind(this)) }.bind(this));
s12.run("MATCH p = (u:User {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN COUNT(DISTINCT(n))", {name:payload}) s11.run("MATCH p = (u:User {name:{name}})-[r1:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'unrolledControl':result.records[0]._fields[0].low}) this.setState({'firstdegreeControl':result.records[0]._fields[0].low});
s12.close() s11.close();
}.bind(this)) }.bind(this));
s13.run("MATCH p = shortestPath((u:User {name:{name}})-[r1:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) RETURN COUNT(DISTINCT(n))", {name:payload}) s12.run("MATCH p = (u:User {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN COUNT(DISTINCT(n))", {name:payload})
.then(function(result){ .then(function(result){
this.setState({'transitiveControl':result.records[0]._fields[0].low}) this.setState({'unrolledControl':result.records[0]._fields[0].low});
s13.close() s12.close();
}.bind(this)) }.bind(this));
this.setState({'driversessions': [s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13]})
}
render() { s13.run("MATCH (n) WHERE NOT n.name={name} WITH n MATCH p = shortestPath((u:User {name:{name}})-[r1:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) RETURN COUNT(DISTINCT(n))", {name:payload})
var domain = '@' + this.state.label.split('@').last() .then(function(result){
return ( this.setState({'transitiveControl':result.records[0]._fields[0].low});
<div className={this.props.visible ? "" : "displaynone"}> s13.close();
<dl className='dl-horizontal'> }.bind(this));
this.setState({'driversessions': [s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,props]});
}
isArray(object){
return object && typeof object === 'object' && object.constructor === Array;
}
convertToDisplayProp(propName){
var obj = this.state.propertyMap[propName];
var type = typeof obj;
if (type === 'undefined'){
return "No Data";
}else if (obj.hasOwnProperty('low')){
var t = obj.low;
if (t === 0){
return "Never";
}else{
return new Date(obj.low * 1000).toUTCString();
}
}else if (type === 'boolean'){
return obj.toString().toTitleCase();
}else if (obj === ""){
return "None";
}else{
return obj;
}
}
render() {
var domain = '@' + this.state.label.split('@').last();
return (
<div className={this.props.visible ? "" : "displaynone"}>
<dl className='dl-horizontal'>
<h4> <h4>
Node Info User Info
</h4> </h4>
<dt> <dt>
Name Name
</dt> </dt>
<dd> <dd>
{this.state.label} {this.state.label}
</dd> </dd>
<dt> <dt>
SAMAccountName Display Name
</dt> </dt>
<dd> <dd>
{this.state.samAccountName} {this.convertToDisplayProp("DisplayName")}
</dd> </dd>
<dt> <dt>
Display Name Password Last Changed
</dt> </dt>
<dd> <dd>
{this.state.displayName} {this.convertToDisplayProp("PwdLastSet")}
</dd> </dd>
<dt> <dt>
Password Last Changed Last Logon
</dt> </dt>
<dd> <dd>
{this.state.pwdLastChanged} {this.convertToDisplayProp("LastLogon")}
</dd> </dd>
<dt> <dt>
Sessions Enabled
</dt> </dt>
<dd> <dd>
<NodeALink {this.convertToDisplayProp("Enabled")}
ready={this.state.sessions !== -1} </dd>
value={this.state.sessions} <dt>
click={function(){ Email
emitter.emit('query', "MATCH (n:Computer)-[r:HasSession]->(m:User {name:{name}}) RETURN n,r,m", {name:this.state.label} </dt>
,this.state.label) <dd>
}.bind(this)} /> {this.convertToDisplayProp("Email")}
</dd> </dd>
<br /> <dt>
<h4>Group Membership</h4> Service Principal Names
<dt> </dt>
First Degree Group Memberships {(() => {
</dt> if (this.state.propertyMap.ServicePrincipalNames.length === 0){
<dd> return <dd>None</dd>;
<NodeALink }
ready={this.state.firstDegreeGroupMembership !== -1} })()}
value={this.state.firstDegreeGroupMembership} {Object.keys(this.state.propertyMap.ServicePrincipalNames).map(function(key){
click={function(){ var x = <dd key={key}>{this.state.propertyMap.ServicePrincipalNames[key]}</dd>;
emitter.emit( return x;
'query', }.bind(this))}
"MATCH p = (n:User {name:{name}})-[r:MemberOf]->(g:Group) RETURN p", {name:this.state.label} <dt>
) Sessions
}.bind(this)} /> </dt>
</dd> <dd>
<dt> <NodeALink
Unrolled Group Memberships ready={this.state.sessions !== -1}
</dt> value={this.state.sessions}
<dd> click={function(){
<NodeALink emitter.emit('query', "MATCH (n:Computer)-[r:HasSession]->(m:User {name:{name}}) RETURN n,r,m", {name:this.state.label}
ready={this.state.unrolledGroupMembership !== -1} ,this.state.label);
value={this.state.unrolledGroupMembership} }.bind(this)}
click={function(){ />
emitter.emit('query', "MATCH p = (n:User {name:{name}})-[r:MemberOf*1..]->(g:Group) RETURN p", {name:this.state.label}, </dd>
this.state.label)
}.bind(this)} /> <h4>Group Membership</h4>
</dd> <dt>
<dt> First Degree Group Memberships
Foreign Group Membership </dt>
</dt> <dd>
<dd> <NodeALink
<NodeALink ready={this.state.firstDegreeGroupMembership !== -1}
ready={this.state.foreignGroupMembership !== -1} value={this.state.firstDegreeGroupMembership}
value={this.state.foreignGroupMembership} click={function(){
click={function(){ emitter.emit(
emitter.emit('query', 'query',
"MATCH (n:Group) WHERE NOT n.name ENDS WITH {domain} WITH n MATCH (m:User {name:{name}}) WITH n,m MATCH p = (m)-[r:MemberOf*1..]->(n) RETURN p", {name: this.state.label, domain: domain}) "MATCH p = (n:User {name:{name}})-[r:MemberOf]->(g:Group) RETURN p", {name:this.state.label}
}.bind(this)} /> );
</dd> }.bind(this)}
<br /> />
<h4> </dd>
Local Admin Rights <dt>
</h4> Unrolled Group Memberships
<dt> </dt>
First Degree Local Admin <dd>
</dt> <NodeALink
<dd> ready={this.state.unrolledGroupMembership !== -1}
<NodeALink value={this.state.unrolledGroupMembership}
ready={this.state.firstDegreeLocalAdmin !== -1} click={function(){
value={this.state.firstDegreeLocalAdmin} emitter.emit('query', "MATCH p = (n:User {name:{name}})-[r:MemberOf*1..]->(g:Group) RETURN p", {name:this.state.label},
click={function(){ this.state.label);
emitter.emit('query', "MATCH p = (n:User {name:{name}})-[r:AdminTo]->(c:Computer) RETURN p", {name:this.state.label}) }.bind(this)}
}.bind(this)} /> />
</dd> </dd>
<dt> <dt>
Group Delegated Local Admin Rights Foreign Group Membership
</dt> </dt>
<dd> <dd>
<NodeALink <NodeALink
ready={this.state.groupDelegatedLocalAdmin !== -1} ready={this.state.foreignGroupMembership !== -1}
value={this.state.groupDelegatedLocalAdmin} value={this.state.foreignGroupMembership}
click={function(){ click={function(){
emitter.emit('query', "MATCH p=(n:User {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AdminTo]->(c:Computer) RETURN p", {name:this.state.label} emitter.emit('query',
,this.state.label) "MATCH (n:Group) WHERE NOT n.name ENDS WITH {domain} WITH n MATCH (m:User {name:{name}}) WITH n,m MATCH p = (m)-[r:MemberOf*1..]->(n) RETURN p", {name: this.state.label, domain: domain});
}.bind(this)} /> }.bind(this)}
</dd> />
<dt> </dd>
Derivative Local Admin Rights
</dt> <h4>
<dd> Local Admin Rights
<NodeALink </h4>
ready={this.state.derivativeLocalAdmin !== -1} <dt>
value={this.state.derivativeLocalAdmin} First Degree Local Admin
click={function(){ </dt>
emitter.emit('query', "MATCH p = shortestPath((n:User {name:{name}})-[r:HasSession|AdminTo|MemberOf*1..]->(c:Computer)) RETURN p", {name:this.state.label} <dd>
,this.state.label) <NodeALink
}.bind(this)} /> ready={this.state.firstDegreeLocalAdmin !== -1}
</dd> value={this.state.firstDegreeLocalAdmin}
<br /> click={function(){
<h4> emitter.emit('query', "MATCH p = (n:User {name:{name}})-[r:AdminTo]->(c:Computer) RETURN p", {name:this.state.label});
Outbound Object Control }.bind(this)}
</h4> />
<dt> </dd>
First Degree Object Control <dt>
</dt> Group Delegated Local Admin Rights
<dd> </dt>
<NodeALink <dd>
ready={this.state.firstdegreeControl !== -1} <NodeALink
value={this.state.firstdegreeControl} ready={this.state.groupDelegatedLocalAdmin !== -1}
click={function(){ value={this.state.groupDelegatedLocalAdmin}
emitter.emit('query', "MATCH p = (u:User {name:{name}})-[r1:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN p", {name:this.state.label}) click={function(){
}.bind(this)} /> emitter.emit('query', "MATCH p=(n:User {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AdminTo]->(c:Computer) RETURN p", {name:this.state.label}
</dd> ,this.state.label);
<dt> }.bind(this)}
Group Delegated Object Control />
</dt> </dd>
<dd> <dt>
<NodeALink Derivative Local Admin Rights
ready={this.state.unrolledControl !== -1} </dt>
value={this.state.unrolledControl} <dd>
click={function(){ <NodeALink
emitter.emit('query', "MATCH p = (u:User {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN p", {name:this.state.label} ready={this.state.derivativeLocalAdmin !== -1}
,this.state.label) value={this.state.derivativeLocalAdmin}
}.bind(this)} /> click={function(){
</dd> emitter.emit('query', "MATCH (c:Computer) WHERE NOT c.name={name} WITH c MATCH p = shortestPath((n:User {name:{name}})-[r:HasSession|AdminTo|MemberOf*1..]->(c)) RETURN p", {name:this.state.label}
<dt> ,this.state.label);
Transitive Object Control }.bind(this)}
</dt> />
<dd> </dd>
<NodeALink
ready={this.state.transitiveControl !== -1} <h4>
value={this.state.transitiveControl} Outbound Object Control
click={function(){ </h4>
emitter.emit('query', "MATCH p = shortestPath((u:User {name:{name}})-[r1:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) RETURN p", {name:this.state.label} <dt>
,this.state.label) First Degree Object Control
}.bind(this)} /> </dt>
</dd> <dd>
<br /> <NodeALink
<h4>Inbound Object Control</h4> ready={this.state.firstdegreeControl !== -1}
<dt> value={this.state.firstdegreeControl}
Explicit Object Controllers click={function(){
</dt> emitter.emit('query', "MATCH p = (u:User {name:{name}})-[r1:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN p", {name:this.state.label});
<dd> }.bind(this)}
<NodeALink />
ready={this.state.firstdegreeControllers !== -1} </dd>
value={this.state.firstdegreeControllers} <dt>
click={function(){ Group Delegated Object Control
emitter.emit('query', "MATCH p = (n)-[r:AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(u1:User {name: {name}}) RETURN p", {name:this.state.label} </dt>
,this.state.label) <dd>
}.bind(this)} /> <NodeALink
</dd> ready={this.state.unrolledControl !== -1}
<dt> value={this.state.unrolledControl}
Unrolled Object Controllers click={function(){
</dt> emitter.emit('query', "MATCH p = (u:User {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(n) RETURN p", {name:this.state.label}
<dd> ,this.state.label);
<NodeALink }.bind(this)}
ready={this.state.unrolledControllers !== -1} />
value={this.state.unrolledControllers} </dd>
click={function(){ <dt>
emitter.emit('query', "MATCH p = (n1)-[r:MemberOf*1..]->(g:Group)-[r1:AddMembers|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(u:User {name: {name}}) WITH LENGTH(p) as pathLength, p, n1 WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.name = u.name) AND NOT n1.name = u.name RETURN p", {name:this.state.label} Transitive Object Control
,this.state.label) </dt>
}.bind(this)} /> <dd>
</dd> <NodeALink
<dt> ready={this.state.transitiveControl !== -1}
Transitive Object Controllers value={this.state.transitiveControl}
</dt> click={function(){
<dd> emitter.emit('query', "MATCH (n) WHERE NOT n.name={name} WITH n MATCH p = shortestPath((u:User {name:{name}})-[r1:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) RETURN p", {name:this.state.label}
<NodeALink ,this.state.label);
ready={this.state.transitiveControllers !== -1} }.bind(this)}
value={this.state.transitiveControllers} />
click={function(){ </dd>
emitter.emit('query', "MATCH p = shortestPath((n1)-[r1:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(u1:User {name: {name}})) RETURN p", {name:this.state.label}
,this.state.label) <h4>Inbound Object Control</h4>
}.bind(this)} /> <dt>
</dd> Explicit Object Controllers
</dl> </dt>
</div> <dd>
); <NodeALink
} ready={this.state.firstdegreeControllers !== -1}
value={this.state.firstdegreeControllers}
click={function(){
emitter.emit('query', "MATCH p = (n)-[r:AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(u1:User {name: {name}}) RETURN p", {name:this.state.label}
,this.state.label);
}.bind(this)}
/>
</dd>
<dt>
Unrolled Object Controllers
</dt>
<dd>
<NodeALink
ready={this.state.unrolledControllers !== -1}
value={this.state.unrolledControllers}
click={function(){
emitter.emit('query', "MATCH p = (n1)-[r:MemberOf*1..]->(g:Group)-[r1:AddMembers|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner]->(u:User {name: {name}}) WITH LENGTH(p) as pathLength, p, n1 WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.name = u.name) AND NOT n1.name = u.name RETURN p", {name:this.state.label}
,this.state.label);
}.bind(this)}
/>
</dd>
<dt>
Transitive Object Controllers
</dt>
<dd>
<NodeALink
ready={this.state.transitiveControllers !== -1}
value={this.state.transitiveControllers}
click={function(){
emitter.emit('query', "MATCH (n1) WHERE NOT n1.name={name} WITH n1 MATCH p = shortestPath((n1)-[r1:MemberOf|AddMembers|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(u1:User {name: {name}})) RETURN p", {name:this.state.label}
,this.state.label);
}.bind(this)}
/>
</dd>
</dl>
</div>
);
}
} }
UserNodeData.propTypes = { UserNodeData.propTypes = {
visible : React.PropTypes.bool.isRequired visible : PropTypes.bool.isRequired
} };

View File

@ -3,7 +3,8 @@
#root { #root {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden;
} }
.max{ .max{
@ -94,6 +95,13 @@ div.tooltip-inner-custom {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: lightgray; background-color: lightgray;
overflow: hidden;
}
.dropdown-item > i{
float:right;
margin-top:3px;
margin-left: 2px;
} }
.graph:focus { .graph:focus {
@ -187,6 +195,12 @@ div.tooltip-inner-custom {
margin-bottom: 1em; margin-bottom: 1em;
} }
.dl-horizontal > h4 {
margin-top: 10px;
margin-bottom: 5px;
color: blue;
}
.dl-horizontal{ .dl-horizontal{
margin-right: 10px; margin-right: 10px;
} }
@ -763,7 +777,6 @@ div.tooltip-inner-custom {
} }
.tab-content > div:nth-last-child(2) > div{ .tab-content > div:nth-last-child(2) > div{
height: 600px; max-height: 600px;
overflow-y: auto; overflow-y: auto;
resize: vertical;
} }

View File

@ -106,10 +106,11 @@ global.appStore = {
'ForceChangePassword': 'tapered', 'ForceChangePassword': 'tapered',
'GenericAll': 'tapered', 'GenericAll': 'tapered',
'GenericWrite': 'tapered', 'GenericWrite': 'tapered',
'WriteDACL': 'tapered', 'WriteDacl': 'tapered',
'WriteOwner': 'tapered', 'WriteOwner': 'tapered',
'AddMembers': 'tapered', 'AddMembers': 'tapered',
'TrustedBy': 'curvedArrow' 'TrustedBy': 'curvedArrow',
'DCSync' : 'tapered'
} }
}, },
lowResPalette: { lowResPalette: {

File diff suppressed because one or more lines are too long

View File

@ -10,32 +10,32 @@ export function generateUniqueId(sigmaInstance, isNode) {
} }
} }
return i return i;
} }
//Recursive function to highlight paths to start/end nodes //Recursive function to highlight paths to start/end nodes
export function findGraphPath(sigmaInstance, reverse, nodeid) { export function findGraphPath(sigmaInstance, reverse, nodeid) {
var target = reverse ? appStore.startNode : appStore.endNode var target = reverse ? appStore.startNode : appStore.endNode;
//This is our stop condition for recursing //This is our stop condition for recursing
if (nodeid !== target.id) { if (nodeid !== target.id) {
var edges = sigmaInstance.graph.adjacentEdges(nodeid) var edges = sigmaInstance.graph.adjacentEdges(nodeid);
var nodes = reverse ? sigmaInstance.graph.inboundNodes(nodeid) : sigmaInstance.graph.outboundNodes(nodeid) var nodes = reverse ? sigmaInstance.graph.inboundNodes(nodeid) : sigmaInstance.graph.outboundNodes(nodeid);
//Loop over the nodes near us and the edges connecting to those nodes //Loop over the nodes near us and the edges connecting to those nodes
$.each(nodes, function(index, node) { $.each(nodes, function(index, node) {
$.each(edges, function(index, edge) { $.each(edges, function(index, edge) {
var check = reverse ? edge.source : edge.target var check = reverse ? edge.source : edge.target;
//If an edge is pointing in the right direction, set its color //If an edge is pointing in the right direction, set its color
//Push the edge into our store and then //Push the edge into our store and then
node = parseInt(node) node = parseInt(node);
if (check === node) { if (check === node) {
edge.color = reverse ? 'blue' : 'red'; edge.color = reverse ? 'blue' : 'red';
appStore.highlightedEdges.push(edge); appStore.highlightedEdges.push(edge);
findGraphPath(sigmaInstance, reverse, node); findGraphPath(sigmaInstance, reverse, node);
} }
}) });
}) });
} else { } else {
return return;
} }
} }
@ -45,192 +45,211 @@ export function clearSessions(){
} }
function deleteSessions(){ function deleteSessions(){
var session = driver.session() var session = driver.session();
session.run("MATCH ()-[r:HasSession]-() WITH r LIMIT 100000 DELETE r RETURN count(r)") session.run("MATCH ()-[r:HasSession]-() WITH r LIMIT 100000 DELETE r RETURN count(r)")
.then(function(results) { .then(function(results) {
session.close() session.close();
emitter.emit("refreshDBData") emitter.emit("refreshDBData");
var count = results.records[0]._fields[0].low var count = results.records[0]._fields[0].low;
if (count === 0) { if (count === 0) {
emitter.emit('hideDBClearModal') emitter.emit('hideDBClearModal');
} else { } else {
deleteSessions(); deleteSessions();
} }
}) });
} }
export function clearDatabase() { export function clearDatabase() {
emitter.emit('openClearingModal'); emitter.emit('openClearingModal');
deleteEdges() deleteEdges();
} }
function deleteEdges() { function deleteEdges() {
var session = driver.session() var session = driver.session();
session.run("MATCH ()-[r]-() WITH r LIMIT 100000 DELETE r RETURN count(r)") session.run("MATCH ()-[r]-() WITH r LIMIT 100000 DELETE r RETURN count(r)")
.then(function(results) { .then(function(results) {
emitter.emit("refreshDBData"); emitter.emit("refreshDBData");
session.close() session.close();
var count = results.records[0]._fields[0].low var count = results.records[0]._fields[0].low;
if (count === 0) { if (count === 0) {
deleteNodes() deleteNodes();
} else { } else {
deleteEdges() deleteEdges();
} }
}) });
} }
function deleteNodes() { function deleteNodes() {
var session = driver.session() var session = driver.session();
session.run("MATCH (n) WITH n LIMIT 100000 DELETE n RETURN count(n)") session.run("MATCH (n) WITH n LIMIT 100000 DELETE n RETURN count(n)")
.then(function(results) { .then(function(results) {
emitter.emit("refreshDBData") emitter.emit("refreshDBData");
session.close() session.close();
var count = results.records[0]._fields[0].low var count = results.records[0]._fields[0].low;
if (count === 0) { if (count === 0) {
emitter.emit('hideDBClearModal') emitter.emit('hideDBClearModal');
} else { } else {
deleteNodes() deleteNodes();
} }
}) });
}
export function findObjectType(header){
if (header.includes('UserName') && header.includes('ComputerName') && header.includes('Weight')){
return 'sessions';
}else if (header.includes('AccountName') && header.includes('AccountType') && header.includes('GroupName')){
return 'groupmembership';
}else if (header.includes('AccountName') && header.includes('AccountType') && header.includes('ComputerName')){
return 'localadmin';
}else if (header.includes('SourceDomain') && header.includes('TargetDomain') && header.includes('TrustDirection') && header.includes('TrustType') && header.includes('Transitive')){
return 'domain';
}else if (header.includes('ActiveDirectoryRights') && header.includes('ObjectType') && header.includes('PrincipalType') && header.includes('PrincipalName') && header.includes('ObjectName') && header.includes('ACEType') && header.includes('AccessControlType') && header.includes('IsInherited')){
return 'acl';
}else if (header.includes('AccountName') && header.includes('Enabled') && header.includes('PwdLastSet') && header.includes('LastLogon') && header.includes('Sid') && header.includes('SidHistory') && header.includes('HasSPN') && header.includes('ServicePrincipalNames')){
return 'userprops';
}else if (header.includes('AccountName') && header.includes('Enabled') && header.includes('PwdLastSet') && header.includes('LastLogon') && header.includes('OperatingSystem') && header.includes('Sid')){
return 'compprops';
}else{
return 'unknown';
}
} }
export function buildGroupMembershipProps(rows) { export function buildGroupMembershipProps(rows) {
var users = [] var users = [];
var groups = [] var groups = [];
var computers = [] var computers = [];
$.each(rows, function(index, row) { $.each(rows, function(index, row) {
switch (row.AccountType) { switch (row.AccountType) {
case 'user': case 'user':
users.push({ account: row.AccountName.toUpperCase(), group: row.GroupName.toUpperCase() }) users.push({ account: row.AccountName.toUpperCase(), group: row.GroupName.toUpperCase() });
break break;
case 'computer': case 'computer':
computers.push({ account: row.AccountName.toUpperCase(), group: row.GroupName.toUpperCase() }) computers.push({ account: row.AccountName.toUpperCase(), group: row.GroupName.toUpperCase() });
break break;
case 'group': case 'group':
groups.push({ account: row.AccountName.toUpperCase(), group: row.GroupName.toUpperCase() }) groups.push({ account: row.AccountName.toUpperCase(), group: row.GroupName.toUpperCase() });
break break;
} }
}) });
return { users: users, groups: groups, computers: computers } return { users: users, groups: groups, computers: computers };
} }
export function buildLocalAdminProps(rows) { export function buildLocalAdminProps(rows) {
var users = [] var users = [];
var groups = [] var groups = [];
var computers = [] var computers = [];
$.each(rows, function(index, row) { $.each(rows, function(index, row) {
if (row.AccountName.startsWith('@')) { if (row.AccountName.startsWith('@')) {
return return;
} }
switch (row.AccountType) { switch (row.AccountType) {
case 'user': case 'user':
users.push({ account: row.AccountName.toUpperCase(), computer: row.ComputerName.toUpperCase() }) users.push({ account: row.AccountName.toUpperCase(), computer: row.ComputerName.toUpperCase() });
break; break;
case 'group': case 'group':
groups.push({ account: row.AccountName.toUpperCase(), computer: row.ComputerName.toUpperCase() }) groups.push({ account: row.AccountName.toUpperCase(), computer: row.ComputerName.toUpperCase() });
break; break;
case 'computer': case 'computer':
computers.push({ account: row.AccountName.toUpperCase(), computer: row.ComputerName.toUpperCase() }) computers.push({ account: row.AccountName.toUpperCase(), computer: row.ComputerName.toUpperCase() });
break break;
} }
}) });
return { users: users, groups: groups, computers: computers } return { users: users, groups: groups, computers: computers };
} }
export function buildSessionProps(rows) { export function buildSessionProps(rows) {
var sessions = [] var sessions = [];
$.each(rows, function(index, row) { $.each(rows, function(index, row) {
if (row.UserName === 'ANONYMOUS LOGON@UNKNOWN' || row.UserName === '') { if (row.UserName === 'ANONYMOUS LOGON@UNKNOWN' || row.UserName === '') {
return return;
} }
sessions.push({ account: row.UserName.toUpperCase(), computer: row.ComputerName.toUpperCase(), weight: row.Weight }) sessions.push({ account: row.UserName.toUpperCase(), computer: row.ComputerName.toUpperCase(), weight: row.Weight });
}) });
return sessions return sessions;
} }
export function buildDomainProps(rows) { export function buildDomainProps(rows) {
var domains = [] var domains = [];
$.each(rows, function(index, row) { $.each(rows, function(index, row) {
switch (row.TrustDirection) { switch (row.TrustDirection) {
case 'Inbound': case 'Inbound':
domains.push({ domain1: row.TargetDomain.toUpperCase(), domain2: row.SourceDomain.toUpperCase(), trusttype: row.TrustType, transitive: row.Transitive }) domains.push({ domain1: row.TargetDomain.toUpperCase(), domain2: row.SourceDomain.toUpperCase(), trusttype: row.TrustType, transitive: row.Transitive });
break; break;
case 'Outbound': case 'Outbound':
domains.push({ domain1: row.SourceDomain.toUpperCase(), domain2: row.TargetDomain.toUpperCase(), trusttype: row.TrustType, transitive: row.Transitive }) domains.push({ domain1: row.SourceDomain.toUpperCase(), domain2: row.TargetDomain.toUpperCase(), trusttype: row.TrustType, transitive: row.Transitive });
break; break;
case 'Bidirectional': case 'Bidirectional':
domains.push({ domain1: row.TargetDomain.toUpperCase(), domain2: row.SourceDomain.toUpperCase(), trusttype: row.TrustType, transitive: row.Transitive }) domains.push({ domain1: row.TargetDomain.toUpperCase(), domain2: row.SourceDomain.toUpperCase(), trusttype: row.TrustType, transitive: row.Transitive });
domains.push({ domain1: row.SourceDomain.toUpperCase(), domain2: row.TargetDomain.toUpperCase(), trusttype: row.TrustType, transitive: row.Transitive }) domains.push({ domain1: row.SourceDomain.toUpperCase(), domain2: row.TargetDomain.toUpperCase(), trusttype: row.TrustType, transitive: row.Transitive });
break break;
} }
}) });
return domains return domains;
} }
export function buildACLProps(rows) { export function buildACLProps(rows) {
var datadict = {} var datadict = {};
$.each(rows, function(index, row) { $.each(rows, function(index, row) {
var b = row.ObjectName.toUpperCase() var b = row.ObjectName.toUpperCase();
var a = row.PrincipalName.toUpperCase() var a = row.PrincipalName.toUpperCase();
var btype = row.ObjectType.toTitleCase() var btype = row.ObjectType.toTitleCase();
var atype = row.PrincipalType.toTitleCase() var atype = row.PrincipalType.toTitleCase();
var rel = row.ActiveDirectoryRights var rel = row.ActiveDirectoryRights;
var extright = row.ACEType var extright = row.ACEType;
var rights = [] var rights = [];
if (extright === 'All'){ if (extright === 'All'){
rights.push("AllExtendedRights") rights.push("AllExtendedRights");
}else if (extright === 'User-Force-Change-Password'){ }else if (extright === 'User-Force-Change-Password'){
rights.push("ForceChangePassword") rights.push("ForceChangePassword");
}else if (rel === "ExtendedRight"){ }else if (rel === "ExtendedRight"){
rights.push(extright) rights.push(extright);
} }
if (rel.includes("GenericAll")){ if (rel.includes("GenericAll")){
rights.push("GenericAll") rights.push("GenericAll");
} }
if (rel.includes("WriteDacl")){ if (rel.includes("WriteDacl")){
rights.push("WriteDacl") rights.push("WriteDacl");
} }
if (rel.includes("WriteOwner")){ if (rel.includes("WriteOwner")){
rights.push("WriteOwner") rights.push("WriteOwner");
} }
if (rel.includes("GenericWrite")){ if (rel.includes("GenericWrite")){
rights.push("GenericWrite") rights.push("GenericWrite");
} }
if (rel.includes("WriteProperty") && extright === "Member"){ if (rel.includes("WriteProperty") && extright === "Member"){
rights.push("AddMember") rights.push("AddMember");
} }
$.each(rights, function(index, record){ $.each(rights, function(index, record){
var hash = (atype + record + btype).toUpperCase() var hash = (atype + record + btype).toUpperCase();
if (btype === 'Computer') { if (btype === 'Computer') {
return return;
} }
if (datadict[hash]) { if (datadict[hash]) {
datadict[hash].props.push({ datadict[hash].props.push({
account: a, account: a,
principal: b principal: b
}) });
} else { } else {
datadict[hash] = { datadict[hash] = {
statement: 'UNWIND {props} AS prop MERGE (a:{} {name:prop.account}) WITH a,prop MERGE (b:{} {name: prop.principal}) WITH a,b,prop MERGE (a)-[r:{} {isACL:true}]->(b)'.format(atype, btype, record), statement: 'UNWIND {props} AS prop MERGE (a:{} {name:prop.account}) WITH a,prop MERGE (b:{} {name: prop.principal}) WITH a,b,prop MERGE (a)-[r:{} {isACL:true}]->(b)'.format(atype, btype, record),
props: [{ account: a, principal: b }] props: [{ account: a, principal: b }]
} };
} }
}) });
});
})
return datadict return datadict;
} }

View File

@ -1,5 +1,5 @@
global.sigma = require('linkurious') global.sigma = require('linkurious');
require('./sigma.helpers.graph.min.js') require('./sigma.helpers.graph.min.js');
Array.prototype.allEdgesSameType = function() { Array.prototype.allEdgesSameType = function() {
for (var i = 1; i < this.length; i++) { for (var i = 1; i < this.length; i++) {
@ -18,192 +18,192 @@ sigma.classes.graph.addMethod('inboundNodes', function(id) {
return this.inNeighborsIndex.get(id).keyList(); return this.inNeighborsIndex.get(id).keyList();
}); });
var sigmaInstance = new sigma() var sigmaInstance = new sigma();
process.on('message', function(m){ process.on('message', function(m){
var data = JSON.parse(m) var data = JSON.parse(m);
params = {edge: data.edge,sibling: data.sibling, start: data.start, end: data.end} params = {edge: data.edge,sibling: data.sibling, start: data.start, end: data.end};
var spotlightData = {} var spotlightData = {};
sigmaInstance.graph.clear() sigmaInstance.graph.clear();
sigmaInstance.graph.read(data.graph) sigmaInstance.graph.read(data.graph);
sigmaInstance.graph.nodes().forEach(function(node){ sigmaInstance.graph.nodes().forEach(function(node){
node.degree = sigmaInstance.graph.degree(node.id) node.degree = sigmaInstance.graph.degree(node.id);
}) });
var result = collapseEdgeNodes(sigmaInstance, params, spotlightData) var result = collapseEdgeNodes(sigmaInstance, params, spotlightData);
sigmaInstance = result[0] sigmaInstance = result[0];
spotlightData = result[1] spotlightData = result[1];
result = collapseSiblingNodes(sigmaInstance, params, spotlightData) result = collapseSiblingNodes(sigmaInstance, params, spotlightData);
sigmaInstance = result[0] sigmaInstance = result[0];
spotlightData = result[1] spotlightData = result[1];
sigmaInstance.graph.nodes().forEach(function(node) { sigmaInstance.graph.nodes().forEach(function(node) {
if (!spotlightData.hasOwnProperty(node.id)) { if (!spotlightData.hasOwnProperty(node.id)) {
spotlightData[node.id] = [node.label, 0, "", node.type, ""]; spotlightData[node.id] = [node.label, 0, "", node.type, ""];
} }
}); });
var toSend = {nodes: sigmaInstance.graph.nodes(), edges: sigmaInstance.graph.edges(), spotlight: spotlightData} var toSend = {nodes: sigmaInstance.graph.nodes(), edges: sigmaInstance.graph.edges(), spotlight: spotlightData};
process.send(toSend) process.send(toSend);
}) });
function collapseEdgeNodes(sigmaInstance, params, spotlightData){ function collapseEdgeNodes(sigmaInstance, params, spotlightData){
var threshold = params.edge; var threshold = params.edge;
if (threshold == 0){ if (threshold == 0){
return [sigmaInstance, spotlightData] return [sigmaInstance, spotlightData];
} }
sigmaInstance.graph.nodes().forEach(function(node){ sigmaInstance.graph.nodes().forEach(function(node){
if (node.degree < threshold){ if (node.degree < threshold){
return return;
} }
sigmaInstance.graph.adjacentNodes(node.id).forEach(function(anode){ sigmaInstance.graph.adjacentNodes(node.id).forEach(function(anode){
if (params.end !== null && anode.label === params.end){ if (params.end !== null && anode.label === params.end){
return return;
} }
if (params.start !== null && anode.label === params.start){ if (params.start !== null && anode.label === params.start){
return return;
} }
var edges = sigmaInstance.graph.adjacentEdges(anode.id); var edges = sigmaInstance.graph.adjacentEdges(anode.id);
if ((edges.length > 1 || edges.length === 0) || (anode.folded.nodes.length > 0)){ if ((edges.length > 1 || edges.length === 0) || (anode.folded.nodes.length > 0)){
return return;
} }
var edge = edges[0]; var edge = edges[0];
if ((anode.type_user) if ((anode.type_user)
|| (anode.type_computer) || (anode.type_computer)
|| (anode.type_group && edge.label === 'AdminTo')){ || (anode.type_group && edge.label === 'AdminTo')){
node.isGrouped = true node.isGrouped = true;
node.folded.nodes.push(anode) node.folded.nodes.push(anode);
node.folded.edges.push(edge) node.folded.edges.push(edge);
spotlightData[anode.id] = [anode.label, node.id, node.label, anode.type, node.type]; spotlightData[anode.id] = [anode.label, node.id, node.label, anode.type, node.type];
sigmaInstance.graph.dropNode(anode.id); sigmaInstance.graph.dropNode(anode.id);
} }
}); });
if (node.folded.nodes.length > 0){ if (node.folded.nodes.length > 0){
node.glyphs.push({ node.glyphs.push({
'position': 'bottom-left', 'position': 'bottom-left',
'content': node.folded.nodes.length 'content': node.folded.nodes.length
}) });
} }
}) });
return [sigmaInstance, spotlightData] return [sigmaInstance, spotlightData];
} }
function collapseSiblingNodes(sigmaInstance, params, spotlightData){ function collapseSiblingNodes(sigmaInstance, params, spotlightData){
var threshold = params.sibling var threshold = params.sibling;
if (threshold === 0){ if (threshold === 0){
return [sigmaInstance, spotlightData] return [sigmaInstance, spotlightData];
} }
sigmaInstance.graph.nodes().forEach(function(node){ sigmaInstance.graph.nodes().forEach(function(node){
//Dont apply this logic to anything thats folded or isn't a computer //Dont apply this logic to anything thats folded or isn't a computer
if (!node.type_computer || node.folded.nodes.length > 0){ if (!node.type_computer || node.folded.nodes.length > 0){
return return;
} }
//Start by getting all the edges attached to this node //Start by getting all the edges attached to this node
var adjacent = sigmaInstance.graph.adjacentEdges(node.id) var adjacent = sigmaInstance.graph.adjacentEdges(node.id);
var siblings = [] var siblings = [];
//Check to see if all the edges are the same type (i.e. AdminTo) //Check to see if all the edges are the same type (i.e. AdminTo)
if (adjacent.length > 1 && adjacent.allEdgesSameType()){ if (adjacent.length > 1 && adjacent.allEdgesSameType()){
//Get the "parents" by mapping the source from every edge //Get the "parents" by mapping the source from every edge
var parents = adjacent.map( var parents = adjacent.map(
function(e){ function(e){
return e.source return e.source;
} }
) );
//Generate our string to compare other nodes to //Generate our string to compare other nodes to
//by sorting the parents and turning it into a string //by sorting the parents and turning it into a string
var checkString = parents.sort().join(',') var checkString = parents.sort().join(',');
var testString; var testString;
//Loop back over nodes in the graph and look for any nodes //Loop back over nodes in the graph and look for any nodes
//with identical parents //with identical parents
sigmaInstance.graph.nodes().forEach(function(node2){ sigmaInstance.graph.nodes().forEach(function(node2){
testString = sigmaInstance.graph.adjacentEdges(node2.id).map( testString = sigmaInstance.graph.adjacentEdges(node2.id).map(
function(e){ function(e){
return e.source; return e.source;
} }
).sort().join(',') ).sort().join(',');
if (testString === checkString){ if (testString === checkString){
siblings.push(node2); siblings.push(node2);
} }
}); });
if (siblings.length >= threshold){ if (siblings.length >= threshold){
//Generate a new ID for our grouped node //Generate a new ID for our grouped node
var nodeId = generateUniqueId(sigmaInstance, true); var nodeId = generateUniqueId(sigmaInstance, true);
sigmaInstance.graph.addNode({ sigmaInstance.graph.addNode({
id: nodeId, id: nodeId,
x: node.x, x: node.x,
y: node.y, y: node.y,
degree: siblings.length, degree: siblings.length,
label: "Grouped Computers", label: "Grouped Computers",
type: 'Computer', type: 'Computer',
type_computer: true, type_computer: true,
groupedNode: true, groupedNode: true,
glyphs: [{ glyphs: [{
position: 'bottom-left', position: 'bottom-left',
content: siblings.length content: siblings.length
}], }],
folded: { folded: {
nodes: [], nodes: [],
edges: [] edges: []
} }
}); });
//Generate new edges for each parent going to our new node //Generate new edges for each parent going to our new node
parents.forEach(function(parent){ parents.forEach(function(parent){
var id = generateUniqueId(sigmaInstance, false); var id = generateUniqueId(sigmaInstance, false);
sigmaInstance.graph.addEdge({ sigmaInstance.graph.addEdge({
id: id, id: id,
source: parent, source: parent,
target: nodeId, target: nodeId,
label: 'AdminTo', label: 'AdminTo',
neo4j_type: 'AdminTo', neo4j_type: 'AdminTo',
size: 1 size: 1
}) });
}) });
var n = sigmaInstance.graph.nodes(nodeId); var n = sigmaInstance.graph.nodes(nodeId);
//Loop over all the siblings, and push the edges into our new parent node //Loop over all the siblings, and push the edges into our new parent node
//Push the nodes in as well so we can unfold them //Push the nodes in as well so we can unfold them
siblings.forEach(function(sibling){ siblings.forEach(function(sibling){
sigmaInstance.graph.adjacentEdges(sibling.id).forEach(function(edge){ sigmaInstance.graph.adjacentEdges(sibling.id).forEach(function(edge){
n.folded.edges.push(edge) n.folded.edges.push(edge);
}) });
n.folded.nodes.push(sibling) n.folded.nodes.push(sibling);
spotlightData[sibling.id] = [sibling.label, nodeId, n.label, sibling.type, n.type]; spotlightData[sibling.id] = [sibling.label, nodeId, n.label, sibling.type, n.type];
sigmaInstance.graph.dropNode(sibling.id) sigmaInstance.graph.dropNode(sibling.id);
}) });
} }
} }
}) });
return [sigmaInstance, spotlightData] return [sigmaInstance, spotlightData];
} }
function generateUniqueId(sigmaInstance, isNode){ function generateUniqueId(sigmaInstance, isNode){
var i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10; var i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10;
if (isNode){ if (isNode){
while (typeof sigmaInstance.graph.nodes(i) !== 'undefined'){ while (typeof sigmaInstance.graph.nodes(i) !== 'undefined'){
i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10; i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10;
} }
}else{ }else{
while (typeof sigmaInstance.graph.edges(i) !== 'undefined'){ while (typeof sigmaInstance.graph.edges(i) !== 'undefined'){
i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10; i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10;
} }
} }
return i return i;
} }