From ec88ff2948956d0b17cc9c3ba27bfa2a3d386225 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Sat, 4 Aug 2018 17:35:51 -0400 Subject: [PATCH] Add Pictures to nodes Fix error on non-image/bad file New loading indicator --- package.json | 10 +- src/AppContainer.jsx | 9 +- src/components/Float/LoadingContainer.jsx | 2 +- src/components/Menu/MenuContainer.jsx | 19 +- .../SearchContainer/SearchContainer.jsx | 2 +- .../SearchContainer/TabContainer.jsx | 24 ++- .../SearchContainer/Tabs/ComputerNodeData.jsx | 153 +++++++++++++- .../SearchContainer/Tabs/DomainNodeData.jsx | 152 ++++++++++++- .../SearchContainer/Tabs/GpoNodeData.jsx | 153 +++++++++++++- .../SearchContainer/Tabs/GroupNodeData.jsx | 200 ++++++++++++++++-- .../SearchContainer/Tabs/OuNodeData.jsx | 152 ++++++++++++- .../SearchContainer/Tabs/PrebuiltQueries.json | 9 + .../SearchContainer/Tabs/SelectedImage.jsx | 40 ++++ .../SearchContainer/Tabs/UserNodeData.jsx | 149 ++++++++++++- src/css/styles.css | 30 ++- src/img/loading_new.gif | Bin 0 -> 1123389 bytes src/index.js | 11 +- 17 files changed, 1077 insertions(+), 38 deletions(-) create mode 100644 src/components/SearchContainer/Tabs/SelectedImage.jsx create mode 100644 src/img/loading_new.gif diff --git a/package.json b/package.json index 63cb84b..8dba747 100644 --- a/package.json +++ b/package.json @@ -52,22 +52,28 @@ "dependencies": { "@fortawesome/fontawesome-free": "^5.1.1", "async": "^2.6.0", + "base64-img": "^1.0.4", "bootstrap": "^3.3.7", "bootstrap-3-typeahead": "^4.0.2", "dagre": "^0.7.4", "electron-store": "^1.3.0", "eventemitter2": "^4.1.0", "fontfaceobserver": "^2.0.13", + "image-size": "^0.6.3", + "image-type": "^3.0.0", "is-zip-file": "^1.0.2", "jquery": "^3.2.1", "linkurious": "^1.5.1", + "md5-file": "^4.0.0", "mustache": "^2.3.0", "neo4j-driver": "^1.6.2", "prop-types": "^15.6.2", - "react": "^16.2.0", + "react": "^16.4.2", "react-bootstrap": "^0.32.0", - "react-dom": "^16.2.0", + "react-dom": "^16.4.2", "react-if": "^2.1.0", + "react-images": "^0.5.19", + "react-photo-gallery": "^6.1.2", "react-transition-group": "^2.2.1", "stream-json": "^1.1.0", "unzipper": "^0.8.9", diff --git a/src/AppContainer.jsx b/src/AppContainer.jsx index 1b00386..3693579 100644 --- a/src/AppContainer.jsx +++ b/src/AppContainer.jsx @@ -31,10 +31,15 @@ export default class AppContainer extends Component { event.preventDefault(); return false; }, false) - + document.addEventListener('drop', function(event){ - emitter.emit("filedrop", event) event.preventDefault(); + if (jQuery("#tabcontainer").has(jQuery(event.target)).length === 1){ + emitter.emit("imageupload", event) + }else{ + emitter.emit("filedrop", event) + } + return false; }, false) } diff --git a/src/components/Float/LoadingContainer.jsx b/src/components/Float/LoadingContainer.jsx index a554076..e493273 100644 --- a/src/components/Float/LoadingContainer.jsx +++ b/src/components/Float/LoadingContainer.jsx @@ -35,7 +35,7 @@ export default class LoadingContainer extends Component { return (
{this.state.text}
- +
); } diff --git a/src/components/Menu/MenuContainer.jsx b/src/components/Menu/MenuContainer.jsx index 0e835a2..4089140 100644 --- a/src/components/Menu/MenuContainer.jsx +++ b/src/components/Menu/MenuContainer.jsx @@ -65,7 +65,7 @@ export default class MenuContainer extends Component { }, 3000 ); - this.addOwnedProp(); + this.addBaseProps(); $.each(results, function(_, file) { if (file.delete) { unlinkSync(file.path); @@ -150,7 +150,7 @@ export default class MenuContainer extends Component { }, 3000 ); - this.addOwnedProp(); + this.addBaseProps(); $.each(results, (_, file) => { if (file.delete) { unlinkSync(file.path); @@ -164,10 +164,11 @@ export default class MenuContainer extends Component { ); } - async addOwnedProp(){ + async addBaseProps(){ let s = driver.session(); await s.run("MATCH (n:User) WHERE NOT EXISTS(n.owned) SET n.owned=false"); await s.run("MATCH (n:Computer) WHERE NOT EXISTS(n.owned) SET n.owned=false"); + await s.run("MATCH (n) WHERE NOT EXISTS(n.pics) SET n.pics=[]"); s.close(); } @@ -229,11 +230,17 @@ export default class MenuContainer extends Component { let size = statSync(file).size; createReadStream(file, {encoding: 'utf8', start: size-100, end: size}).on('data', chunk => { - type = /type.?:\s?"(\w*)"/g.exec(chunk)[1]; - count = /count.?:\s?(\d*)/g.exec(chunk)[1]; + let type; + try{ + type = /type.?:\s?"(\w*)"/g.exec(chunk)[1]; + count = /count.?:\s?(\d*)/g.exec(chunk)[1]; + }catch(e){ + type = null; + } + if (!acceptableTypes.includes(type)){ - emitter.emit("showAlert", "Unrecognized JSON Type"); + emitter.emit("showAlert", "Unrecognized File"); this.setState({ uploading: false }); diff --git a/src/components/SearchContainer/SearchContainer.jsx b/src/components/SearchContainer/SearchContainer.jsx index d7f7a22..362486e 100644 --- a/src/components/SearchContainer/SearchContainer.jsx +++ b/src/components/SearchContainer/SearchContainer.jsx @@ -812,7 +812,7 @@ export default class SearchContainer extends Component { -
+
diff --git a/src/components/SearchContainer/TabContainer.jsx b/src/components/SearchContainer/TabContainer.jsx index d87074e..dafb089 100644 --- a/src/components/SearchContainer/TabContainer.jsx +++ b/src/components/SearchContainer/TabContainer.jsx @@ -9,6 +9,8 @@ import DomainNodeData from "./Tabs/DomainNodeData"; import GpoNodeData from "./Tabs/GpoNodeData"; import OuNodeData from "./Tabs/OuNodeData"; import { Tabs, Tab } from "react-bootstrap"; +import { openSync, readSync, closeSync } from "fs"; +import imageType from "image-type"; export default class TabContainer extends Component { constructor(props) { @@ -32,6 +34,24 @@ export default class TabContainer extends Component { emitter.on("domainNodeClicked", this._domainNodeClicked.bind(this)); emitter.on("gpoNodeClicked", this._gpoNodeClicked.bind(this)); emitter.on("ouNodeClicked", this._ouNodeClicked.bind(this)); + emitter.on("imageupload", this.uploadImage.bind(this)); + } + + uploadImage(event){ + let files = []; + $.each(event.dataTransfer.files, (_, f) => { + let buf = Buffer.alloc(12); + let file = openSync(f.path, 'r') + readSync(file,buf, 0, 12, 0); + closeSync(file) + let type = imageType(buf); + if (type !== null && type.mime.includes("image")){ + files.push({path: f.path, name: f.name}) + }else{ + emitter.emit("showAlert", `${f.name} is not an image`); + } + }) + emitter.emit("imageUploadFinal", files); } _userNodeClicked() { @@ -136,9 +156,7 @@ export default class TabContainer extends Component { /> - + diff --git a/src/components/SearchContainer/Tabs/ComputerNodeData.jsx b/src/components/SearchContainer/Tabs/ComputerNodeData.jsx index 0f166ae..fda174c 100644 --- a/src/components/SearchContainer/Tabs/ComputerNodeData.jsx +++ b/src/components/SearchContainer/Tabs/ComputerNodeData.jsx @@ -4,6 +4,16 @@ import NodeProps from "./NodeProps"; import NodeCypherLink from "./NodeCypherLink"; import NodeCypherNoNumberLink from "./NodeCypherNoNumberLink"; import NodeCypherLinkComplex from "./NodeCypherLinkComplex"; +import Gallery from "react-photo-gallery"; +import SelectedImage from "./SelectedImage"; +import Lightbox from "react-images"; +import { readFileSync, writeFileSync } from "fs"; +import { base64Sync } from "base64-img"; +import sizeOf from "image-size"; +import md5File from "md5-file"; +import { remote } from "electron"; +const { app } = remote; +import { join } from "path"; export default class ComputerNodeData extends Component { constructor() { @@ -20,14 +30,26 @@ export default class ComputerNodeData extends Component { unconstraineddelegation: "Allows Unconstrained Delegation", owned: "Compromised" }, - notes: null + notes: null, + pics: [], + currentImage: 0, + lightboxIsOpen: false }; emitter.on("computerNodeClicked", this.getNodeData.bind(this)); + emitter.on("imageUploadFinal", this.uploadImage.bind(this)); + emitter.on("clickPhoto", this.openLightbox.bind(this)); + emitter.on("deletePhoto", this.handleDelete.bind(this)); + } + + componentDidMount() { + jQuery(this.refs.complete).hide(); + jQuery(this.refs.piccomplete).hide(); } getNodeData(payload) { jQuery(this.refs.complete).hide(); + jQuery(this.refs.piccomplete).hide(); $.each(this.state.driversessions, function(_, record) { record.close(); }); @@ -37,6 +59,15 @@ export default class ComputerNodeData extends Component { driversessions: [] }); + let key = `computer_${this.state.label}`; + let c = imageconf.get(key); + let pics = []; + if (typeof c !== "undefined"){ + this.setState({pics: c}) + }else{ + this.setState({pics: pics}) + } + var propCollection = driver.session(); propCollection .run("MATCH (c:Computer {name:{name}}) RETURN c", { name: payload }) @@ -75,6 +106,96 @@ export default class ComputerNodeData extends Component { this.setState({ driversessions: [propCollection] }); } + uploadImage(files) { + if (!this.props.visible || files.length === 0) { + return; + } + let p = this.state.pics; + let oLen = p.length; + let key = `computer_${this.state.label}`; + + $.each(files, (_, f) => { + let exists = false; + let hash = md5File.sync(f.path); + $.each(p, (_, p1) => { + if (p1.hash === hash){ + exists = true; + } + }) + if (exists){ + emitter.emit("showAlert", "Image already exists"); + return; + } + let path = join(app.getPath("userData"), "images", hash); + let dimensions = sizeOf(f.path); + let data = readFileSync(f.path); + writeFileSync(path, data); + p.push({hash: hash, src: path, width: dimensions.width, height: dimensions.height}) + }); + + if (p.length === oLen){ + return; + } + this.setState({pics: p}); + imageconf.set(key, p) + let check = jQuery(this.refs.piccomplete); + check.show(); + check.fadeOut(2000); + } + + handleDelete(event) { + if (!this.props.visible) { + return; + } + let pics = this.state.pics; + let temp = pics[event.index]; + pics.splice(event.index, 1); + this.setState({ + pics: pics + }) + let key = `computer_${this.state.label}`; + imageconf.set(key, pics); + + let check = jQuery(this.refs.piccomplete); + check.show(); + check.fadeOut(2000); + } + + openLightbox(event) { + if (!this.props.visible) { + return; + } + this.setState({ + currentImage: event.index, + lightboxIsOpen: true + }); + } + closeLightbox() { + if (!this.props.visible) { + return; + } + this.setState({ + currentImage: 0, + lightboxIsOpen: false + }); + } + gotoPrevious() { + if (!this.props.visible) { + return; + } + this.setState({ + currentImage: this.state.currentImage - 1 + }); + } + gotoNext() { + if (!this.props.visible) { + return; + } + this.setState({ + currentImage: this.state.currentImage + 1 + }); + } + notesChanged(event){ this.setState({notes: event.target.value}) } @@ -97,6 +218,28 @@ export default class ComputerNodeData extends Component { } render() { + let gallery; + if (this.state.pics.length === 0){ + gallery = (Drop pictures on here to upload!) + }else{ + gallery = ( + + + + ) + } + return (
@@ -278,6 +421,14 @@ export default class ComputerNodeData extends Component { />