diff --git a/BloodHoundExampleDB.graphdb/neostore.counts.db.a b/BloodHoundExampleDB.graphdb/neostore.counts.db.a
index 98be43307..cda798b25 100644
Binary files a/BloodHoundExampleDB.graphdb/neostore.counts.db.a and b/BloodHoundExampleDB.graphdb/neostore.counts.db.a differ
diff --git a/BloodHoundExampleDB.graphdb/neostore.transaction.db.0 b/BloodHoundExampleDB.graphdb/neostore.transaction.db.0
index 0fc9007e2..c76ae782f 100644
Binary files a/BloodHoundExampleDB.graphdb/neostore.transaction.db.0 and b/BloodHoundExampleDB.graphdb/neostore.transaction.db.0 differ
diff --git a/index.html b/index.html
index e8d0ef4e5..c35f12433 100644
--- a/index.html
+++ b/index.html
@@ -16,7 +16,6 @@
     <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/bootstrap-3-typeahead/bootstrap3-typeahead.min.js" type="text/javascript"></script>
-    <script src="node_modules/neo4j-driver/lib/browser/neo4j-web.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>
     <link type="text/css" rel="stylesheet" href="src/css/simple-slider.css">
diff --git a/package.json b/package.json
index f5784053e..6c1e00d21 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "bloodhound",
-  "version": "1.1.0",
+  "version": "1.2.0",
   "description": "Graph Theory for Active Directory",
   "keywords": [
     "Graph",
@@ -34,16 +34,16 @@
     ]
   },
   "devDependencies": {
-    "babel-cli": "^6.11.4",
-    "babel-core": "^6.11.4",
-    "babel-loader": "^6.2.4",
-    "babel-polyfill": "^6.9.1",
-    "babel-preset-es2015": "^6.9.0",
-    "babel-preset-react": "^6.11.1",
-    "babel-preset-stage-0": "^6.5.0",
-    "concurrently": "^2.2.0",
-    "cross-env": "^2.0.0",
-    "electron": "^1.4.3",
+    "babel-cli": "^6.22.2",
+    "babel-core": "^6.22.1",
+    "babel-loader": "*",
+    "babel-polyfill": "^6.22.0",
+    "babel-preset-es2015": "^6.22.0",
+    "babel-preset-react": "^6.22.0",
+    "babel-preset-stage-0": "^6.22.0",
+    "concurrently": "^3.1.0",
+    "cross-env": "^3.1.4",
+    "electron": "^1.4.15",
     "express": "^4.14.0",
     "webpack": "^1.13.1",
     "webpack-dev-middleware": "^1.6.1",
@@ -51,19 +51,20 @@
     "webpack-target-electron-renderer": "^0.4.0"
   },
   "dependencies": {
+    "async": "^2.1.4",
     "bootstrap": "^3.3.6",
     "bootstrap-3-typeahead": "^4.0.1",
-    "configstore": "^2.0.0",
+    "configstore": "^2.1.0",
     "dagre": "^0.7.4",
-    "eventemitter2": "^2.0.0",
+    "eventemitter2": "^2.2.2",
     "jquery": "^2.2.4",
     "linkurious": "^1.5.1",
     "mustache": "^2.2.1",
-    "neo4j-driver": "^1.0.4",
-    "react": "^15.3.1",
-    "react-addons-css-transition-group": "^15.3.1",
+    "neo4j-driver": "^1.1.0",
+    "react": "^15.4.2",
+    "react-addons-css-transition-group": "^15.4.2",
     "react-bootstrap": "^0.30.3",
-    "react-dom": "^15.3.1",
+    "react-dom": "^15.4.2",
     "react-if": "^2.1.0"
   }
 }
diff --git a/src/AppContainer.jsx b/src/AppContainer.jsx
index a43e350d9..17fd0c73f 100644
--- a/src/AppContainer.jsx
+++ b/src/AppContainer.jsx
@@ -15,7 +15,9 @@ import ExportContainer from './components/Float/ExportContainer';
 import Settings from './components/Float/Settings'
 import ZoomContainer from './components/Zoom/ZoomContainer'
 import QueryNodeSelect from './components/Float/QueryNodeSelect'
+import SessionClearModal from './components/Modals/SessionClearModal'
 import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
+import About from './components/Modals/About.jsx'
 
 export default class AppContainer extends Component {
 	constructor(){
@@ -41,11 +43,13 @@ export default class AppContainer extends Component {
 					<ClearConfirmModal />
 					<ClearingModal />
 					<CancelUploadModal />
+					<SessionClearModal />
 					<RawQuery />
 					<MenuContainer />
 					<Settings />
 					<ZoomContainer />
 					<QueryNodeSelect />
+					<About />
 				</div>
 			</ReactCSSTransitionGroup>
 		);
diff --git a/src/components/Float/Login.jsx b/src/components/Float/Login.jsx
index 7c5120ead..05f6e1d5e 100644
--- a/src/components/Float/Login.jsx
+++ b/src/components/Float/Login.jsx
@@ -7,8 +7,6 @@ export default class Login extends Component {
 			url: "",
 			icon: null,
 			loginEnabled: false,
-			dbHelpVisible: false,
-			loginHelpVisible: false,
 			user: "",
 			password: "",
 			loginInProgress: false
@@ -18,6 +16,8 @@ export default class Login extends Component {
 	checkDBPresence(){
 		var url = this.state.url;
 		var icon = this.state.icon;
+		var jicon = jQuery(icon)
+		var btn = jQuery(this.refs.loginButton)
 
 		if (url === ""){
 			return;
@@ -38,28 +38,41 @@ export default class Login extends Component {
 		icon.removeClass();
 		icon.addClass("fa fa-spinner fa-spin form-control-feedback");
 		icon.toggle(true);
-		var driver = neo4j.v1.driver(url)
-		var session = driver.session()
-
-		session.run('MATCH (n) RETURN (n) LIMIT 1')
-			.subscribe({
-				onNext: function(next){
-				},
-				onError: function(error){
-					if (error.code){
-						this.setState({dbHelpVisible: true})
-						icon.removeClass();
-                    	icon.addClass("fa fa-times-circle red-icon-color form-control-feedback");
-					}else{
-						icon.removeClass();
-						icon.addClass("fa fa-check-circle green-icon-color form-control-feedback");
-						this.setState({loginEnabled: true, url: url})
-					}
-				}.bind(this),
-				onComplete: function(){
-					session.close()
-				}
-			})
+		var driver = neo4j.driver(url)
+		driver.onCompleted = function(){
+			driver.close()
+		}
+		driver.onError = function(error){
+			console.log(error)
+			if (error.message && error.message.includes("encryption certificate has changed")){
+				var path = error.message.match("`(.*?)`")[1]
+				icon.removeClass();
+				icon.addClass("fa fa-times-circle red-icon-color form-control-feedback");
+				icon.attr('data-original-title', 'Certificate error - delete localhost line in {}'.format(path))
+					.tooltip('fixTitle')
+					.tooltip('show')
+				this.setState({
+					loginInProgress: false,
+					loginEnabled: false
+				})
+			}else if (error.fields && error.fields[0].code === "Neo.ClientError.Security.Unauthorized"){
+				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
+				})
+			}
+			driver.close()
+		}.bind(this)
+		driver.session();
 	}
 
 	checkDBCreds(){
@@ -68,24 +81,80 @@ export default class Login extends Component {
 		}
 		this.setState({
 			loginInProgress: true,
-			loginHelpVisible: false,
 			loginEnabled: false
 		})
 
 		var btn = jQuery(this.refs.loginButton)
+		var pwf = jQuery(this.refs.password)
 
-		var driver = neo4j.v1.driver(this.state.url, neo4j.v1.auth.basic(this.state.user, this.state.password),{knownHosts: 'known_hosts'})
-		var session = driver.session()
+		var driver = neo4j.driver(this.state.url, neo4j.auth.basic(this.state.user, this.state.password))
+		driver.onError = function(error){
+			if (error.fields && error.fields[0].code === "Neo.ClientError.Security.Unauthorized"){
+				btn.removeClass('activate');
+				this.setState({
+					loginInProgress: false,
+					loginEnabled: true
+				})
+				pwf.attr('data-original-title', 'Invalid username or password')
+					.tooltip('fixTitle')
+					.tooltip('show')
+			}else if (error.fields && error.fields[0].code === "Neo.ClientError.Security.AuthenticationRateLimit"){
+				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.message && error.message.includes("encryption certificate has changed")){
+				var path = error.message.match("`(.*?)`")[1]
+				var icon = this.state.icon
+				icon.toggle('true')
+				icon.removeClass();
+				icon.addClass("fa fa-times-circle red-icon-color form-control-feedback");
+				jQuery(icon).tooltip({
+					placement : 'right',
+					title: 'Certificate error - delete localhost line in ' + path,
+					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>'
+				})
+				this.setState({
+					loginInProgress: false,
+					loginEnabled: false
+				})
+				jQuery(icon).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.toggleClass('activate');
-					this.setState({
-						loginHelpVisible: true,
-						loginInProgress: false,
-						loginEnabled: true
-					})
-					
+					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(){
 
@@ -103,14 +172,16 @@ export default class Login extends Component {
 						user: this.state.user,
 						password: this.state.password
 					})
-					global.driver = driver
 					appStore.databaseInfo = conf.get('databaseInfo');
+					jQuery(this.refs.password).tooltip('hide')
+					jQuery(this.refs.urlspinner).tooltip('hide')
 					setTimeout(function(){
 						jQuery(this.refs.outer).fadeOut(400, function(){
 							renderEmit.emit('login');
 						});
 					}.bind(this), 1500)
-					session.close()
+					driver.close()
+					global.driver = neo4j.driver(this.state.url, neo4j.auth.basic(this.state.user, this.state.password))
 				}.bind(this)
 			})
 
@@ -130,8 +201,23 @@ export default class Login extends Component {
 	}
 
 	componentDidMount() {
-		jQuery(this.refs.urlspinner).toggle(false)
+		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();
 		}
@@ -142,11 +228,13 @@ export default class Login extends Component {
 	}
 
 	_userChanged(event){
-		this.setState({user: event.target.value})	
+		this.setState({user: event.target.value})
+		jQuery(this.refs.password).tooltip('hide')
 	}
 
 	_passChanged(event){
-		this.setState({password: event.target.value})	
+		this.setState({password: event.target.value})
+		jQuery(this.refs.password).tooltip('hide')
 	}
 
 	_triggerLogin(e){
@@ -171,10 +259,9 @@ export default class Login extends Component {
 								<span className="input-group-addon" id="dburladdon">
 									Database URL
 								</span>
-								<input ref="url" onFocus={function(){this.setState({dbHelpVisible: false})}.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" />
+								<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" />
 								<i ref="urlspinner" className="fa fa-spinner fa-spin form-control-feedback" />
 							</div>
-							{this.state.dbHelpVisible ? <p className="help-block help-block-add">No Neo4j Database Found</p> : null}
 							<div className="input-group spacing">
 								<span className="input-group-addon" id="dbuseraddon">DB Username</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" />
@@ -183,7 +270,6 @@ export default class Login extends Component {
 								<span className="input-group-addon" id="dbpwaddon">DB Password</span>
 								<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>
-							{this.state.loginHelpVisible ? <p className="help-block help-block-add" style={{color: "#d9534f"}}>Wrong username or password</p> : null}
 							<button ref="loginButton" disabled={!this.state.loginEnabled} type="button" onClick={this.checkDBCreds.bind(this)} className="btn btn-primary loginbutton has-spinner">
 								Login
 								<span className="button-spinner">
diff --git a/src/components/Graph.jsx b/src/components/Graph.jsx
index 61c1515f8..693cf2e4e 100644
--- a/src/components/Graph.jsx
+++ b/src/components/Graph.jsx
@@ -209,6 +209,7 @@ export default class GraphContainer extends Component {
 
     clearGraph(){
         this.state.sigmaInstance.graph.clear()
+        this.state.sigmaInstance.refresh()
     }
 
     setGraphicsMode(){
@@ -764,8 +765,10 @@ export default class GraphContainer extends Component {
         });
 
         dagreListener.bind('stop', function(event){
-            emitter.emit('updateLoadingText', "Fixing Overlap");
-            sigmaInstance.startNoverlap();
+            emitter.emit('updateLoadingText', 'Done!');
+            setTimeout(function(){
+                emitter.emit('showLoadingIndicator', false);    
+            }, 1500)
         })
 
         dagreListener.bind('start', function(event){
diff --git a/src/components/Menu/MenuContainer.jsx b/src/components/Menu/MenuContainer.jsx
index aac42e0b4..391707f52 100644
--- a/src/components/Menu/MenuContainer.jsx
+++ b/src/components/Menu/MenuContainer.jsx
@@ -1,10 +1,11 @@
 import React, { Component } from 'react';
 import MenuButton from './MenuButton';
 import ProgressBarMenuButton from './ProgressBarMenuButton';
-import { buildDomainProps, buildSessionProps, buildLocalAdminProps, buildGroupMembershipProps } from 'utils';
+import { buildDomainProps, buildSessionProps, buildLocalAdminProps, buildGroupMembershipProps, buildACLProps } from 'utils';
 import { If, Then, Else } from 'react-if';
 const { dialog, clipboard } = require('electron').remote
 var fs = require('fs')
+var async = require('async')
 
 export default class MenuContainer extends Component {
 	constructor(){
@@ -51,136 +52,198 @@ export default class MenuContainer extends Component {
 		}
 	}
 
+	_settingsClick(){
+		emitter.emit('openSettings')
+	}
+
+	_cancelUploadClick(){
+		emitter.emit('showCancelUpload')
+	}
+
 	_uploadClick(){
-		var filename = dialog.showOpenDialog({
-			properties: ['openFile']
-		})[0]
+		var input = jQuery(this.refs.fileInput)
+		var files = $.makeArray(input[0].files)
+
+		async.eachSeries(files, function(file, callback){
+			emitter.emit('showAlert', 'Processing file {}'.format(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('')
+	}
+
+	_aboutClick(){
+		emitter.emit('showAbout')
+	}
+
+	processFile(filename, fileobject, callback){
 		var sent = 0
-		fs.readFile(filename, 'utf8', function(err, data){
-			var count = data.split('\n').length;
-			var header = data.split('\n')[0]
-			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'
-			}
-
-			if (typeof filetype === 'undefined'){
-				emitter.emit('showAlert', 'Unrecognized CSV Type');
-				return;
-			}
-
-			this.setState({
-				uploading: true,
-				progress: 0
-			})
 
-			Papa.parse(data,{
-				header: true,
-				dynamicTyping: true,
-				chunkSize: 50000,
-				skipEmptyLines: true,
-				chunk: function(rows, parser){
-					this.setState({parser: parser})
-					if (rows.data.length === 0){
-						parser.abort()
-						this.setState({progress:100})
-						setTimeout(function(){
-							this.setState({uploading: false})
-						}.bind(this), 3000)
-						emitter.emit('refreshDBData')
-						return
+		var i;
+		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])
 					}
-					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 s1 = driver.session()
-						var s2 = driver.session()
-						var s3 = driver.session()
-						var p1
-						var p2
-						var p3
-						p1 = s1.run(userQuery, {props: props.users})
-						p1.then(function(){
-							s1.close()
-							p2 = s2.run(computerQuery, {props: props.computers})
-							p2.then(function(){
-								s2.close()
-								p3 = s3.run(groupQuery, {props: props.groups})
-								p3.then(function(){
-									s3.close()
+					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'){
+					emitter.emit('showAlert', 'Unrecognized CSV Type');
+					return;
+				}
+
+				this.setState({
+					uploading: true,
+					progress: 0
+				})
+				//I have no idea why this workaround is needed. Apparently all my sessions freeze unless I make a random query
+				setTimeout(function(){
+					var sess = driver.session()
+					sess.run('MATCH (n) RETURN (n) LIMIT 1')
+						.then(function(){
+							sess.close()
+						})
+				}, 1000)
+
+				console.time('IngestTime')
+				Papa.parse(fileobject,{
+					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))
-							}.bind(this))
-						}.bind(this))
-					}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 s1 = driver.session()
-						var s2 = driver.session()
-						var s3 = driver.session()
-						var p1
-						var p2
-						var p3
-						p1 = s1.run(userQuery, {props: props.users})
-						p1.then(function(){
-							s1.close()
-							p2 = s2.run(computerQuery, {props: props.computers})
-							p2.then(function(){
-								s2.close()
-								p3 = s3.run(groupQuery, {props: props.groups})
-								p3.then(function(){
-									s3.close()
+						}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}))
+							promises.push(tx.run(computerQuery, {props: props.computers}))
+							promises.push(tx.run(groupQuery, {props: props.groups}))
+
+							Promise.all(promises)
+								.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 === '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()
+							var tx = session.beginTransaction()
+							var promises = []
+
+							promises.push(tx.run(userQuery, {props: props.users}))
+							promises.push(tx.run(computerQuery, {props: props.computers}))
+							promises.push(tx.run(groupQuery, {props: props.groups}))
+
+							Promise.all(promises)
+								.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))
-							}.bind(this))
-						}.bind(this))
-					}else{
-						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))
-					}
-				}.bind(this)
-			})
-		}.bind(this))
-	}
-
-	_settingsClick(){
-		emitter.emit('openSettings')
-	}
+						}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)
+							}
 
-	_cancelUploadClick(){
-		emitter.emit('showCancelUpload')
+							Promise.all(promises)
+								.then(function(){
+									tx.commit()
+										.then(function(){
+											this.setState({progress: Math.floor((sent / count) * 100)})
+											session.close()
+											parser.resume()
+										}.bind(this))
+								}.bind(this))
+						}
+					}.bind(this)
+				})
+			}.bind(this));
 	}
 
 	render() {
@@ -201,7 +264,7 @@ export default class MenuContainer extends Component {
 							<ProgressBarMenuButton click={this._cancelUploadClick.bind(this)} progress={this.state.progress} committed={this.state.committed}/>
 						</Then>
 						<Else>{ () =>
-							<MenuButton click={this._uploadClick.bind(this)} hoverVal="Upload Data" glyphicon="glyphicon glyphicon-upload" />		
+							<MenuButton click={function(){jQuery(this.refs.fileInput).click()}.bind(this)} hoverVal="Upload Data" glyphicon="glyphicon glyphicon-upload" />		
 						}</Else>
 					</If>		
 				</div>
@@ -211,6 +274,10 @@ export default class MenuContainer extends Component {
 				<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>
 		);
 	}
diff --git a/src/components/Modals/About.jsx b/src/components/Modals/About.jsx
new file mode 100644
index 000000000..ab06c2397
--- /dev/null
+++ b/src/components/Modals/About.jsx
@@ -0,0 +1,67 @@
+import React, {Component} from 'react';
+
+var Modal = require('react-bootstrap').Modal;
+var path = require('path')
+var fs = require('fs')
+const { app, shell } = require('electron').remote
+
+
+export default class componentName extends Component {
+    constructor(){
+		super();
+
+		var json = JSON.parse(fs.readFileSync(path.join(app.getAppPath(),'package.json')));
+
+		fs.readFile(path.join(app.getAppPath(),'LICENSE.md'), 'utf8', function(err, data){
+			this.setState({
+				license: data
+			})
+		}.bind(this));
+
+		this.state = {
+			open: false,
+			version: json.version
+		}
+	}
+
+    closeModal(){
+		this.setState({ open: false })
+	}
+
+    openModal(){
+		this.setState({open: true})
+	}
+
+    componentDidMount() {
+		emitter.on('showAbout', this.openModal.bind(this))		
+	}
+
+    render() {
+        return (
+            <Modal
+				show={this.state.open}
+				onHide={this.closeModal.bind(this)}
+				aria-labelledby="AboutHeader">
+
+				<Modal.Header closeButton={true}>
+					<Modal.Title id="AboutHeader">About BloodHound</Modal.Title>
+				</Modal.Header>
+
+				<Modal.Body>
+					<h5><b>Version:</b> {this.state.version}</h5>
+					<h5><b>Github:</b> <a href="#" onClick={function(){shell.openExternal("https://www.github.com/adaptivethreat/BloodHound")}}>https://www.github.com/adaptivethreat/BloodHound</a></h5>
+					<h5><b>Authors:</b> <a href="#" onClick={function(){shell.openExternal("https://www.twitter.com/harmj0y")}}>@harmj0y</a>, <a href="#" onClick={function(){shell.openExternal("https://www.twitter.com/_wald0")}}>@_wald0</a>, <a href="#" onClick={function(){shell.openExternal("https://www.twitter.com/cptjesus")}}>@cptjesus</a></h5>
+					<br />
+					<h5><b>License</b></h5>
+					<div className="aboutscroll">{this.state.license}</div>
+				</Modal.Body>
+
+				<Modal.Footer>
+					<button type="button" className="btn btn-primary" onClick={this.closeModal.bind(this)}>
+						Close
+					</button>
+				</Modal.Footer>
+			</Modal>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/components/Modals/ClearingModal.jsx b/src/components/Modals/ClearingModal.jsx
index 8911097fd..d2e2941ad 100644
--- a/src/components/Modals/ClearingModal.jsx
+++ b/src/components/Modals/ClearingModal.jsx
@@ -30,7 +30,7 @@ export default class ClearingModal extends Component {
 				aria-labelledby="ClearingModalHeader">
 
 				<Modal.Header closeButton={true}>
-					<Modal.Title id="ClearingModalHeader">Clearing Database</Modal.Title>
+					<Modal.Title id="ClearingModalHeader">Clearing Data</Modal.Title>
 				</Modal.Header>
 			</Modal>
 		);
diff --git a/src/components/Modals/LogoutModal.jsx b/src/components/Modals/LogoutModal.jsx
index d7b11185e..82a88ae30 100644
--- a/src/components/Modals/LogoutModal.jsx
+++ b/src/components/Modals/LogoutModal.jsx
@@ -23,8 +23,7 @@ export default class LogoutModal extends Component {
 		this.setState({ open: false })
 		emitter.emit('doLogout');
 		driver.close()
-		ReactDOM.unmountComponentAtNode(document.getElementById('root'))
-		ReactDOM.render(<Login />, document.getElementById('root'))
+		renderEmit.emit('logout')
 	}
 
 	openModal(){
diff --git a/src/components/Modals/SessionClearModal.jsx b/src/components/Modals/SessionClearModal.jsx
new file mode 100644
index 000000000..dcaf8f9fc
--- /dev/null
+++ b/src/components/Modals/SessionClearModal.jsx
@@ -0,0 +1,54 @@
+import React, { Component } from 'react';
+import { clearSessions } from 'utils'
+
+var Modal = require('react-bootstrap').Modal;
+
+export default class SessionClearModal extends Component {
+	constructor(){
+		super();
+
+		this.state = {
+			open: false
+		}
+	}
+
+	closeModal(){
+		this.setState({open: false})
+	}
+
+	openModal(){
+		this.setState({open: true})
+	}
+
+	closeAndClear(){
+		this.setState({ open: false })
+        clearSessions();
+	}
+
+	componentDidMount(){
+		emitter.on('openSessionClearModal', this.openModal.bind(this))
+	}
+
+	render() {
+		return (
+			<Modal
+				show={this.state.open}
+				onHide={this.closeModal.bind(this)}
+				aria-labelledby="SessionModalHeader">
+
+				<Modal.Header closeButton={true}>
+					<Modal.Title id="SessionModalHeader">Clear Sessions</Modal.Title>
+				</Modal.Header>
+
+				<Modal.Body>
+					<p>Are you sure you want to clear sessions?</p>
+				</Modal.Body>
+
+				<Modal.Footer>
+					<button onClick={this.closeAndClear.bind(this)} type="button" className="btn btn-danger">Clear Sessions</button>
+					<button onClick={this.closeModal.bind(this)} type="button" className="btn btn-primary">Cancel</button>
+				</Modal.Footer>
+			</Modal>
+		);
+	}
+}
diff --git a/src/components/SearchContainer/Tabs/DatabaseDataDisplay.jsx b/src/components/SearchContainer/Tabs/DatabaseDataDisplay.jsx
index f350e4f94..3da5a5a7d 100644
--- a/src/components/SearchContainer/Tabs/DatabaseDataDisplay.jsx
+++ b/src/components/SearchContainer/Tabs/DatabaseDataDisplay.jsx
@@ -43,6 +43,10 @@ export default class DatabaseDataDisplay extends Component {
 	toggleDBWarnModal(){
 		emitter.emit('openDBWarnModal')
 	}
+
+	toggleSessionClearModal(){
+		emitter.emit('openSessionClearModal')
+	}
 	
 
 	render() {
@@ -67,8 +71,9 @@ export default class DatabaseDataDisplay extends Component {
 				</dl>
 
 				<div className="text-center">
-					<div className="btn-group dbbuttons">
+					<div className="btn-group btn-group-sm dbbuttons">
 						<button type="button" className="btn btn-success" onClick={function(){this.refreshDBData()}.bind(this)}>Refresh DB Stats</button>
+						<button type="button" className="btn btn-info" onClick={this.toggleSessionClearModal}>Clear Sessions</button>
 						<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>
diff --git a/src/components/SearchContainer/Tabs/PrebuiltQueriesDisplay.jsx b/src/components/SearchContainer/Tabs/PrebuiltQueriesDisplay.jsx
index 474ec6f00..627b6d9e8 100644
--- a/src/components/SearchContainer/Tabs/PrebuiltQueriesDisplay.jsx
+++ b/src/components/SearchContainer/Tabs/PrebuiltQueriesDisplay.jsx
@@ -1,17 +1,51 @@
 import React, { Component } from 'react';
 import PrebuiltQueryNode from './PrebuiltQueryNode'
+import { If, Then, Else } from 'react-if';
+const { app } = require('electron').remote
 var fs = require('fs')
+var path = require('path')
+var process = require('process')
+var exec = require('child_process').exec;
 
 export default class PrebuiltQueriesDisplay extends Component {
     constructor(){
         super();
 
         this.state = {
-            queries: []
+            queries: [],
+            custom: []
         }
     }
 
+    getCommandLine() {
+        switch (process.platform) { 
+            case 'darwin' : return 'open';
+            case 'win32' : return 'start';
+            case 'win64' : return 'start';
+            default : return 'xdg-open';
+        }
+    }
+
+    editCustom(){
+        exec(this.getCommandLine() + ' ' + path.join(app.getPath('userData'),'/customqueries.json'));
+    }
+
     componentWillMount() {
+        $.ajax({
+            url: path.join(app.getPath('userData'),'/customqueries.json'),
+            type: 'GET',
+            success: function(response){
+                var x = JSON.parse(response)
+                var y = []
+
+                $.each(x.queries, function(index, el) {
+                    y.push(el)
+                });
+                
+                this.setState({custom: y})
+            }.bind(this)
+        })
+
         $.ajax({
             url: 'src/components/SearchContainer/Tabs/PrebuiltQueries.json',
             type: 'GET',
@@ -39,6 +73,23 @@ export default class PrebuiltQueriesDisplay extends Component {
                         return <PrebuiltQueryNode key={a.name} info={a} />
                     })}
                 </div>
+                <h3>
+                    Custom Queries
+                    <i className="glyphicon glyphicon-pencil customQueryGlyph" onClick={this.editCustom.bind(this)}></i>
+                </h3>
+                <div className="query-box">
+                    <If condition={ this.state.custom.length == 0}>
+                    <Then><div>No user defined queries.</div></Then>
+                    <Else>{() =>
+                        <div>
+                        {this.state.custom.map(function(a){
+                            return <PrebuiltQueryNode key={a.name} info={a} />
+                        })}
+                        </div>
+                    }
+                    </Else>
+                    </If>
+                </div>
             </div>
         );
     }
diff --git a/src/css/styles.css b/src/css/styles.css
index d7e22ff7c..ed48b0439 100644
--- a/src/css/styles.css
+++ b/src/css/styles.css
@@ -32,6 +32,25 @@ body {
     background-color: lightgray;
 }
 
+.customQueryGlyph{
+    font-size: small;
+    vertical-align: middle;
+    top: -1px;
+    left: 5px;
+    cursor: pointer;
+}
+
+div.tooltip-inner-custom {
+    max-width: 250px;
+}
+
+.aboutscroll{
+    width: 100%;
+    overflow: auto;
+    height: 150px;
+    white-space: pre-wrap;
+}
+
 .queryInput {
     position: relative;
     left: 50%;
diff --git a/src/index.js b/src/index.js
index a9398ec53..0ccf57ebb 100644
--- a/src/index.js
+++ b/src/index.js
@@ -6,25 +6,35 @@ import AppContainer from './AppContainer';
 import Login from './components/Float/Login'
 import { getStorageData, storageHasKey, storageSetKey } from './js/utils.js';
 
+const { app } = require('electron').remote
+var fs = require('fs')
+const path = require('path');
+
 const ConfigStore = require('configstore');
 
 global.conf = new ConfigStore('bloodhound')
 var e = require('eventemitter2').EventEmitter2
 global.emitter = new e({})
 global.renderEmit = new e({})
+global.neo4j = require('neo4j-driver').v1;
 
 global.Mustache = require('mustache')
 
-String.prototype.format = function () {
-  var i = 0, args = arguments;
-  return this.replace(/{}/g, function () {
-    return typeof args[i] != 'undefined' ? args[i++] : '';
-  });
+String.prototype.format = function() {
+    var i = 0,
+        args = arguments;
+    return this.replace(/{}/g, function() {
+        return typeof args[i] != 'undefined' ? args[i++] : '';
+    });
+};
+
+String.prototype.formatAll = function() {
+    var args = arguments;
+    return this.replace(/{}/g, args[0]);
 };
 
-String.prototype.formatAll = function () {
-  var args = arguments;
-  return this.replace(/{}/g, args[0]);
+String.prototype.toTitleCase = function() {
+    return this.replace(/\w\S*/g, function(txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); });
 };
 
 Array.prototype.allEdgesSameType = function() {
@@ -37,8 +47,8 @@ Array.prototype.allEdgesSameType = function() {
     return true;
 };
 
-if (!Array.prototype.last){
-    Array.prototype.last = function(){
+if (!Array.prototype.last) {
+    Array.prototype.last = function() {
         return this[this.length - 1];
     };
 };
@@ -54,16 +64,16 @@ sigma.classes.graph.addMethod('inboundNodes', function(id) {
 });
 
 global.appStore = {
-	dagre: false,
-	startNode: null,
-	endNode: null,
-	highlightedEdges: [],
-	spotlightData: {},
-	queryStack: [],
-	currentTooltip: null,
-	highResPalette: {
-		iconScheme: {
-			'User': {
+    dagre: false,
+    startNode: null,
+    endNode: null,
+    highlightedEdges: [],
+    spotlightData: {},
+    queryStack: [],
+    currentTooltip: null,
+    highResPalette: {
+        iconScheme: {
+            'User': {
                 font: 'FontAwesome',
                 content: '\uF007',
                 scale: 1.5,
@@ -89,89 +99,103 @@ global.appStore = {
             }
         },
         edgeScheme: {
-        	'AdminTo': 'tapered',
-        	'MemberOf': 'tapered',
-        	'HasSession': 'tapered',
-        	'TrustedBy' : 'curvedArrow'
+            'AdminTo': 'tapered',
+            'MemberOf': 'tapered',
+            'HasSession': 'tapered',
+            'TrustedBy': 'curvedArrow'
         }
-	},
-	lowResPalette: {
-		colorScheme: {
-			'User' : '#17E625',
-			'Computer' : '#E67873',
-			'Group' : '#DBE617',
-			'Domain' : '#17E6B9'
-		},
+    },
+    lowResPalette: {
+        colorScheme: {
+            'User': '#17E625',
+            'Computer': '#E67873',
+            'Group': '#DBE617',
+            'Domain': '#17E6B9'
+        },
         edgeScheme: {
-        	'AdminTo': 'line',
-        	'MemberOf': 'line',
-        	'HasSession': 'line',
-        	'TrustedBy' : 'curvedArrow'
+            'AdminTo': 'line',
+            'MemberOf': 'line',
+            'HasSession': 'line',
+            'TrustedBy': 'curvedArrow'
+        }
+    },
+    highResStyle: {
+        nodes: {
+            label: {
+                by: 'label'
+            },
+            size: {
+                by: 'degree',
+                bins: 5,
+                min: 10,
+                max: 20
+            },
+            icon: {
+                by: 'type',
+                scheme: 'iconScheme'
+            }
+        },
+        edges: {
+            type: {
+                by: 'type',
+                scheme: 'edgeScheme'
+            }
         }
-	},
-	highResStyle: {
-		nodes: {
-			label: {
-				by: 'label'
-			},
-			size: {
-				by: 'degree',
-				bins: 5,
-				min: 10,
-				max: 20
-			},
-			icon: {
-				by: 'type',
-				scheme: 'iconScheme'
-			}
-		},
-		edges: {
-			type : {
-				by : 'type',
-				scheme: 'edgeScheme'
-			}
-		}
-	},
-	lowResStyle: {
-		nodes: {
-			label: {
-				by: 'label'
-			},
-			size: {
-				by: 'degree',
-				bins: 10,
-				min: 10,
-				max: 20
-			},
-			color: {
-				by: 'type',
-				scheme: 'colorScheme'
-			}
-		},
-		edges: {
-			type : {
-				by : 'type',
-				scheme: 'edgeScheme'
-			}
-		}
-	}
+    },
+    lowResStyle: {
+        nodes: {
+            label: {
+                by: 'label'
+            },
+            size: {
+                by: 'degree',
+                bins: 10,
+                min: 10,
+                max: 20
+            },
+            color: {
+                by: 'type',
+                scheme: 'colorScheme'
+            }
+        },
+        edges: {
+            type: {
+                by: 'type',
+                scheme: 'edgeScheme'
+            }
+        }
+    }
 }
 
-if (typeof conf.get('performance') === 'undefined'){
-	conf.set('performance', {
-		edge: 5,
-		sibling: 10,
-		lowGraphics: false,
-		nodeLabels: 1
-	})
+if (typeof conf.get('performance') === 'undefined') {
+    conf.set('performance', {
+        edge: 5,
+        sibling: 10,
+        lowGraphics: false,
+        nodeLabels: 1
+    })
 }
 
+var custompath = path.join(app.getPath('userData'), 'customqueries.json')
+
+fs.stat(custompath, function(err, stats) {
+    if (err) {
+        fs.writeFile(custompath, "[]")
+    }
+})
+
 appStore.performance = conf.get('performance')
 
-renderEmit.on('login', function(){
-	emitter.removeAllListeners()
-	ReactDOM.unmountComponentAtNode(document.getElementById('root'))
-	ReactDOM.render(<AppContainer />, document.getElementById('root'))	
+renderEmit.on('login', function() {
+    emitter.removeAllListeners()
+    ReactDOM.unmountComponentAtNode(document.getElementById('root'))
+    ReactDOM.render( < AppContainer / > , document.getElementById('root'))
+})
+
+renderEmit.on('logout', function() {
+    emitter.removeAllListeners()
+    ReactDOM.unmountComponentAtNode(document.getElementById('root'))
+    ReactDOM.render( < Login / > , document.getElementById('root'))
 })
 
-ReactDOM.render(<Login />, document.getElementById('root'))
\ No newline at end of file
+ReactDOM.render( < Login / > , document.getElementById('root'))
\ No newline at end of file
diff --git a/src/js/utils.js b/src/js/utils.js
index bc839217e..53252ebbc 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -1,149 +1,206 @@
-export function generateUniqueId(sigmaInstance, isNode){
-	var i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10;
-	if (isNode){
-		while (typeof sigmaInstance.graph.nodes(i) !== 'undefined'){
-			i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10;
-		}
-	}else{
-		while (typeof sigmaInstance.graph.edges(i) !== 'undefined'){
-			i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10;
-		}
-	}
-
-	return i
+export function generateUniqueId(sigmaInstance, isNode) {
+    var i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10;
+    if (isNode) {
+        while (typeof sigmaInstance.graph.nodes(i) !== 'undefined') {
+            i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10;
+        }
+    } else {
+        while (typeof sigmaInstance.graph.edges(i) !== 'undefined') {
+            i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10;
+        }
+    }
+
+    return i
 }
 
 //Recursive function to highlight paths to start/end nodes
-export function findGraphPath(sigmaInstance, reverse, nodeid){
-	var target = reverse ? appStore.startNode : appStore.endNode
-	//This is our stop condition for recursing
-	if (nodeid !== target.id){
-		var edges = sigmaInstance.graph.adjacentEdges(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
-		$.each(nodes, function(index, node){
-			$.each(edges, function(index, edge){
-				var check = reverse ? edge.source : edge.target
-				//If an edge is pointing in the right direction, set its color
-				//Push the edge into our store and then 
-				node = parseInt(node)
-				if (check === node){
-					edge.color = reverse ? 'blue' : 'red';
-					appStore.highlightedEdges.push(edge);
-					findGraphPath(sigmaInstance, reverse, node);
-				}
-			})
-		})
-	}else{
-		return
-	}
+export function findGraphPath(sigmaInstance, reverse, nodeid) {
+    var target = reverse ? appStore.startNode : appStore.endNode
+        //This is our stop condition for recursing
+    if (nodeid !== target.id) {
+        var edges = sigmaInstance.graph.adjacentEdges(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
+        $.each(nodes, function(index, node) {
+            $.each(edges, function(index, edge) {
+                var check = reverse ? edge.source : edge.target
+                    //If an edge is pointing in the right direction, set its color
+                    //Push the edge into our store and then 
+                node = parseInt(node)
+                if (check === node) {
+                    edge.color = reverse ? 'blue' : 'red';
+                    appStore.highlightedEdges.push(edge);
+                    findGraphPath(sigmaInstance, reverse, node);
+                }
+            })
+        })
+    } else {
+        return
+    }
+}
+
+export function clearSessions(){
+    emitter.emit('openClearingModal');
+    deleteSessions();
+}
+
+function deleteSessions(){
+    var session = driver.session()
+    session.run("MATCH ()-[r:HasSession]-() WITH r LIMIT 100000 DELETE r RETURN count(r)")
+        .then(function(results) {
+            session.close()
+            emitter.emit("refreshDBData")
+            var count = results.records[0]._fields[0].low
+            if (count === 0) {
+                emitter.emit('hideDBClearModal')
+            } else {
+                deleteSessions();
+            }
+        })
+}
+
+export function clearDatabase() {
+    emitter.emit('openClearingModal');
+    deleteEdges()
 }
 
-export function clearDatabase(){
-	emitter.emit('openClearingModal');
-	deleteEdges()
+function deleteEdges() {
+    var session = driver.session()
+    session.run("MATCH ()-[r]-() WITH r LIMIT 100000 DELETE r RETURN count(r)")
+        .then(function(results) {
+            emitter.emit("refreshDBData");
+            session.close()
+            var count = results.records[0]._fields[0].low
+            if (count === 0) {
+                deleteNodes()
+            } else {
+                deleteEdges()
+            }
+        })
 }
 
-function deleteEdges(){
-	var session = driver.session()
-	session.run("MATCH ()-[r]-() WITH r LIMIT 100000 DELETE r RETURN count(r)")
-		.then(function(results){
-			session.close()
-			var count = results.records[0]._fields[0].low
-			if (count === 0){
-				deleteNodes()
-			}else{
-				deleteEdges()
-			}
-		})
+function deleteNodes() {
+    var session = driver.session()
+    session.run("MATCH (n) WITH n LIMIT 100000 DELETE n RETURN count(n)")
+        .then(function(results) {
+            emitter.emit("refreshDBData")
+            session.close()
+            var count = results.records[0]._fields[0].low
+            if (count === 0) {
+                emitter.emit('hideDBClearModal')
+            } else {
+                deleteNodes()
+            }
+        })
 }
 
-function deleteNodes(){
-	var session = driver.session()
-	session.run("MATCH (n) WITH n LIMIT 100000 DELETE n RETURN count(n)")
-		.then(function(results){
-			session.close()
-			var count = results.records[0]._fields[0].low
-			if (count === 0){
-				emitter.emit('hideDBClearModal')
-			}else{
-				deleteNodes()
-			}
-		})
+export function buildGroupMembershipProps(rows) {
+    var users = []
+    var groups = []
+    var computers = []
+    $.each(rows, function(index, row) {
+        switch (row.AccountType) {
+            case 'user':
+                users.push({ account: row.AccountName.toUpperCase(), group: row.GroupName.toUpperCase() })
+                break
+            case 'computer':
+                computers.push({ account: row.AccountName.toUpperCase(), group: row.GroupName.toUpperCase() })
+                break
+            case 'group':
+                groups.push({ account: row.AccountName.toUpperCase(), group: row.GroupName.toUpperCase() })
+                break
+        }
+    })
+
+    return { users: users, groups: groups, computers: computers }
 }
 
-export function buildGroupMembershipProps(rows){
-	var users = []
-	var groups = []
-	var computers = []
-	$.each(rows, function(index, row){
-		switch (row.AccountType){
-			case 'user':
-				users.push({account:row.AccountName.toUpperCase(), group: row.GroupName.toUpperCase()})
-				break
-			case 'computer':
-				computers.push({account:row.AccountName.toUpperCase(), group: row.GroupName.toUpperCase()})
-				break
-			case 'group':
-				groups.push({account:row.AccountName.toUpperCase(), group: row.GroupName.toUpperCase()})
-				break
-		}
-	})
-
-	return {users: users, groups: groups, computers: computers}
+export function buildLocalAdminProps(rows) {
+    var users = []
+    var groups = []
+    var computers = []
+    $.each(rows, function(index, row) {
+        if (row.AccountName.startsWith('@')) {
+            return
+        }
+        switch (row.AccountType) {
+            case 'user':
+                users.push({ account: row.AccountName.toUpperCase(), computer: row.ComputerName.toUpperCase() })
+                break;
+            case 'group':
+                groups.push({ account: row.AccountName.toUpperCase(), computer: row.ComputerName.toUpperCase() })
+                break;
+            case 'computer':
+                computers.push({ account: row.AccountName.toUpperCase(), computer: row.ComputerName.toUpperCase() })
+                break
+        }
+    })
+    return { users: users, groups: groups, computers: computers }
 }
 
-export function buildLocalAdminProps(rows){
-	var users = []
-	var groups = []
-	var computers = []
-	$.each(rows, function(index, row){
-		if (row.AccountName.startsWith('@')){
-			return
-		}
-		switch(row.AccountType){
-			case 'user':
-				users.push({account:row.AccountName.toUpperCase(), computer: row.ComputerName.toUpperCase()})
-				break;
-			case 'group':
-				groups.push({account:row.AccountName.toUpperCase(), computer: row.ComputerName.toUpperCase()})
-				break;
-			case 'computer':
-				computers.push({account:row.AccountName.toUpperCase(), computer: row.ComputerName.toUpperCase()})
-				break
-		}
-	})
-	return {users: users, groups: groups, computers: computers}
+export function buildSessionProps(rows) {
+    var sessions = []
+    $.each(rows, function(index, row) {
+        if (row.UserName === 'ANONYMOUS LOGON@UNKNOWN' || row.UserName === '') {
+            return
+        }
+        sessions.push({ account: row.UserName.toUpperCase(), computer: row.ComputerName.toUpperCase(), weight: row.Weight })
+    })
+
+    return sessions
 }
 
-export function buildSessionProps(rows){
-	var sessions = []
-	$.each(rows, function(index, row){
-		if (row.UserName === 'ANONYMOUS LOGON@UNKNOWN' || row.UserName === ''){
-			return
-		}
-		sessions.push({account: row.UserName.toUpperCase(), computer: row.ComputerName.toUpperCase(), weight: row.Weight})
-	})
+export function buildDomainProps(rows) {
+    var domains = []
+    $.each(rows, function(index, row) {
+        switch (row.TrustDirection) {
+            case 'Inbound':
+                domains.push({ domain1: row.TargetDomain.toUpperCase(), domain2: row.SourceDomain.toUpperCase(), trusttype: row.TrustType, transitive: row.Transitive })
+                break;
+            case 'Outbound':
+                domains.push({ domain1: row.SourceDomain.toUpperCase(), domain2: row.TargetDomain.toUpperCase(), trusttype: row.TrustType, transitive: row.Transitive })
+                break;
+            case 'Bidirectional':
+                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 })
+                break
+        }
+    })
 
-	return sessions
+    return domains
 }
 
-export function buildDomainProps(rows){
-	var domains = []
-	$.each(rows, function(index, row){
-		switch(row.TrustDirection){
-			case 'Inbound':
-				domains.push({domain1: row.TargetDomain.toUpperCase(), domain2: row.SourceDomain.toUpperCase(), trusttype: row.TrustType, transitive: row.Transitive})
-				break;
-			case 'Outbound':
-				domains.push({domain1: row.SourceDomain.toUpperCase(), domain2: row.TargetDomain.toUpperCase(), trusttype: row.TrustType, transitive: row.Transitive})
-				break;
-			case 'Bidirectional':
-				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})
-				break
-		}
-	})
-
-	return domains
+export function buildACLProps(rows) {
+    var datadict = {}
+
+    $.each(rows, function(index, row) {
+        var b = row.ObjectName.toUpperCase()
+        var a = row.PrincipalName.toUpperCase()
+        var btype = row.ObjectType.toTitleCase()
+        var atype = row.PrincipalType.toTitleCase()
+        var rel = row.ActiveDirectoryRights
+
+        var hash = (atype + rel + btype).toUpperCase()
+        if (btype === 'Computer') {
+            return
+        }
+
+        if (rel.includes(' ')) {
+            rel = 'WriteDACL'
+        }
+
+        if (datadict[hash]) {
+            datadict[hash].props.push({
+                account: a,
+                principal: b
+            })
+        } else {
+            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:{}]->(b)'.format(atype, btype, rel),
+                props: [{ account: a, principal: b }]
+            }
+        }
+    })
+
+    return datadict
 }
\ No newline at end of file