Skip to content
Snippets Groups Projects
Commit 19b7e88c authored by Václav Bartoš's avatar Václav Bartoš
Browse files

First version of thehive_button plugin

parent 45122784
No related branches found
No related tags found
No related merge requests found
---
extends: "@elastic/kibana"
settings:
import/resolver:
'@elastic/eslint-import-resolver-kibana':
rootPackageName: 'thehive_button'
npm-debug.log*
node_modules
/build/
/public/app.css
{
}
# The Hive Button
> Visualisation plugin which creates a simple button to create a new case in The Hive.
import newCaseRoute from './server/routes/newcase';
export default function (kibana) {
return new kibana.Plugin({
require: [], //['elasticsearch'],
name: 'thehive_button',
uiExports: {
visTypes: [
'plugins/thehive_button/main',
],
},
init(server, options) { // eslint-disable-line no-unused-vars
// Add server routes and initialize the plugin here
newCaseRoute(server);
}
});
}
{
"name": "thehive_button",
"version": "0.1.1",
"description": "Visualisation plugin which creates a simple button to create a new case in The Hive.",
"main": "index.js",
"kibana": {
"version": "7.2.2"
},
"scripts": {
"lint": "eslint .",
"start": "plugin-helpers start",
"build": "plugin-helpers build"
},
"dependencies": {
"request": "^2.88.0"
},
"devDependencies": {
"@elastic/eslint-config-kibana": "link:../../packages/eslint-config-kibana",
"@elastic/eslint-import-resolver-kibana": "link:../../packages/kbn-eslint-import-resolver-kibana",
"@kbn/plugin-helpers": "link:../../packages/kbn-plugin-helpers",
"babel-eslint": "^9.0.0",
"eslint": "^5.6.0",
"eslint-plugin-babel": "^5.2.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jest": "^21.26.2",
"eslint-plugin-jsx-a11y": "^6.1.2",
"eslint-plugin-mocha": "^5.2.0",
"eslint-plugin-no-unsanitized": "^3.0.2",
"eslint-plugin-prefer-object-spread": "^1.2.1",
"eslint-plugin-react": "^7.11.1",
"expect.js": "^0.3.1"
}
}
// Default plugin configuration
export const THEHIVE_URL = 'https://hive.gn4-3-wp8-soc.sunet.se/';
export const THEHIVE_API_KEY = '5LymseWiurZBrQN8Kqp8O+9KniTL5cE0';
export const THEHIVE_OWNER = 'admin'; // default owner account of the created cases
import './vis.less';
import optionsTemplate from './options_template.html';
import { THEHIVE_API_KEY, THEHIVE_URL, THEHIVE_OWNER } from './env';
import { VisController } from './vis_controller';
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import { Status } from 'ui/vis/update_status';
function TheHiveButtonVisProvider(Private) {
const VisFactory = Private(VisFactoryProvider);
console.log("default URL:", THEHIVE_URL);
console.log("default API key:", THEHIVE_API_KEY);
return VisFactory.createBaseVisualization({
name: 'thehive_button',
title: 'The Hive Case',
icon: 'alert',
description: 'A button to create a new Case in The Hive.',
visualization: VisController,
requiresUpdateStatus: [Status.PARAMS, Status.RESIZE, Status.UI_STATE],
visConfig: {
defaults: {
// add default parameters
url: THEHIVE_URL,
apikey: THEHIVE_API_KEY,
owner: THEHIVE_OWNER,
},
},
editor: 'default',
// editor: MyEditorController,
editorConfig: {
// optionsTemplate: '<div>TEST</div>',
optionsTemplate: optionsTemplate
},
requestHandler: 'none',
responseHandler: 'none',
});
}
// UNUSED
/*class MyEditorController {
constructor(el, vis) {
this.el = el;
this.vis = vis;
//this.config = vis.type.editorConfig;
this.container = document.createElement('div');
this.container.className = 'myvis-config-container-div';
this.el.appendChild(this.container);
}
async render(visData) {
console.log("MyEditorController render(), config:", this.vis);
// console.log(this.vis.isEditorMode);
// console.log(this.vis.getUiState);
// console.log(this.vis.getState);
// console.log(this.vis.params);
this.container.innerHTML = optionsTemplate;
// this.vis.updateState();
this.vis.dirty = true;
return 'done rendering';
}
// destroy() {
// console.log('destroying');
// }
}*/
// register the provider with the visTypes registry
VisTypesRegistryProvider.register(TheHiveButtonVisProvider);
<div class="form-group">
<p style="margin-bottom: 1em">The plugin is currently not configurable from here. Below are the predefined configuration values. They can be edited in "<tt>&lt;KIBANA_PATH&gt;/plugins/thehive_button/public/env.js</tt>" on the backend.</p>
<label>The Hive API URL</label>
<input ng-model=vis.params.url class=form-control disabled=disabled />
<label>The Hive API key</label>
<input ng-model=vis.params.apikey class=form-control disabled=disabled />
</div>
.myvis-container-div {
padding: 1em;
}
import { Status } from 'ui/vis/update_status';
import chrome from 'ui/chrome';
import { toastNotifications } from 'ui/notify';
//import vis_template from './vis_template.html';
import React from 'react';
import {
EuiButton,
// EuiPanel,
} from '@elastic/eui';
function createTheHiveButton(vis) {
var button = document.createElement('button');
button.className = 'euiButton euiButton--danger euiButton--fill';
button.innerHTML = '<span class="euiButton__content"><span class="euiButton__text" title="Create new Case in The Hive">Create new Case</span></span>';
button.addEventListener('click', hiveButtonOnClick);
button._vis = vis; // store reference to 'vis' to the element
// var button2 = <EuiButton fill iconType="alert" color="danger" onClick={(evt) => hiveButtonOnClick(evt, vis.params)}>Create new Case</EuiButton>;
// button2.setState({vis: vis});
return button;
}
function hiveButtonOnClick(evt) {
var button = evt.currentTarget;
var params = button._vis.params;
//console.log("OnCLick", params);
// Add "loading" icon and disable the button
var loading_span = document.createElement('span');
loading_span.className = "euiLoadingSpinner euiLoadingSpinner--medium euiButton__spinner";
button.firstChild.insertBefore(loading_span, button.firstChild.childNodes[0]);
button.setAttribute("disabled", true);
var title = "TODO: SET THE TITLE";
var descr = "(Created from Kibana)\n\n...";
var severity = 2;
var start_date = null;
var owner = params.owner;
var flag = false;
var tlp = 2;
var tags = [];
createHiveCase(params.url, params.apikey, title, descr, severity, start_date, owner, flag, tlp, tags)
.then(function(value) {
if ('error' in value) {
// Error contacting The Hive
console.log("createHiveCase() ERROR:", value.error);
toastNotifications.addDanger("ERROR: " + value.error);
}
else {
// Success - show notification and open the Case in new tab
console.log("createHiveCase() completed:", value);
const case_url = params.url + "index.html#/case/" + value.id + "/details";
toastNotifications.add({
title: "Case created",
color: "success",
iconType: "checkInCircleFilled",
text: (
<div>
<p><b><a href={case_url} target="_blank">Edit the new Case</a></b></p>
</div>
),
});
window.open(case_url, '_blank');
}
// remove "loading" icon and re-enable the button
button.firstChild.removeChild(loading_span);
button.removeAttribute("disabled");
});
return false;
}
// Create a new Case in The Hive via its API
// Return a Promise which resolves to object with ID of the new case ('id' attr) or error message ('error' attr)
function createHiveCase(base_url, api_key, title, descr, severity, startDate, owner, flag, tlp, tags) {
// Prepare data
// TODO: check '/' at the end of base URL
var data = JSON.stringify({
"base_url": base_url,
"api_key": api_key,
"body": {
"title": title,
"description": descr,
"severity": severity, // number: 1=low, 2=medium, 3=high
"startDate": startDate,
"owner": owner, // user name the case will be assigned to
"flag": flag, // bool
"tlp": tlp, // number: 0=white, 1=green, 2=amber, 3=red
"tags": tags, // array of strings
}
});
console.log("Sending request to Kibana API endpoint of thehive_button plugin:", data);
var kibana_endpoint_url = chrome.addBasePath('/api/thehive_button/new_case');
return new Promise(function (resolve, reject) {
// Create AJAX request
var xhr = new XMLHttpRequest();
// Listener to process reply
xhr.onreadystatechange = function () {
if (this.readyState != 4) {
return; // response not ready yet
}
if (this.status == 200) {
const resp = JSON.parse(this.responseText);
console.log("Response from The Hive:", resp);
if ("error" in resp) {
resolve({"error": resp.error});
}
else if (resp.status_code != 201) {
resolve({"error": "Unexpected reply received from The Hive: [" + resp.status_code + "] " + resp.status_msg});
}
else {
resolve({"id": resp.body.id}); // return ID of the new case
}
}
else {
console.log("Error " + this.status + ": " + this.statusText);
resolve({"error": "Error " + this.status + ": " + this.statusText});
}
}
// Send the AJAX request
xhr.open("POST", kibana_endpoint_url);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("kbn-xsrf", "thehive_plugin"); // this header must be set, although its content is probably irrelevant
xhr.send(data);
});
}
class VisController {
constructor(el, vis) {
this.vis = vis;
this.el = el;
//console.log('constructor called!');
this.container = document.createElement('div');
this.container.className = 'myvis-container-div';
this.button = createTheHiveButton(vis);
this.container.appendChild(this.button);
this.el.appendChild(this.container);
}
destroy() {
this.el.innerHTML = '';
}
async render(visData, status) {
//console.log('in promise!', this.vis)
//console.log(vis_template);
//var vis = this.vis;
//this.container.innerHTML = `${vis_template}`;
//this.container.innerHTML += '<p>Hello World from Promise! ' + JSON.stringify(status) + '</p>';
//console.log('Render: ' + JSON.stringify(status));
return 'done rendering';
}
};
export { VisController };
const request = require('request');
export default function (server) {
server.route({
path: '/api/thehive_button/new_case',
method: 'POST',
handler: newCaseHandler,
});
}
// Handler of ajax requests to create a new Case in The Hive
function newCaseHandler(req, resp) {
// Parse the request to get connection parameters
// (everything is configured in forntend and sent as part of the request,
// since I don't know how to configure the backend)
var base_url = req.payload['base_url'];
var api_key = req.payload['api_key'];
var req_body = req.payload['body'];
// check it's a valid URL with slash at the end
if (!base_url) {
return {'error': 'Base URL not set'};
}
if (!base_url.match(/https?:\/\/(([a-z\d.-]+)|((\d{1,3}\.){3}\d{1,3}))(\:\d+)?(\/[-a-z\d%_.~+]*)*\//i)) {
//if (!base_url.match(/https?:\/\/.*\//)) {
return {'error': 'Invalid base URL (it must begin with "http[s]" and end with "/")'};
}
if (!api_key) {
return {'error': 'API key not set'};
}
return new Promise( function(resolve, reject) {
request({
method: 'POST',
url: base_url + 'api/case',
auth: {'bearer': api_key},
json: true,
body: req_body,
//rejectUnauthorized: false, // Disables server certificate check
},
// handler of the reply from The Hive - just return as reply
function (error, response, body) {
// TODO: find out how to set response code, for now we always return sucess and encode original status code in the content
if (error) {
console.log("ERROR when trying to send request to The Hive:", error);
resolve({'error': error.message});
}
else {
if (response.statusCode < 200 || response.statusCode >= 300) {
console.log("ERROR Unexpected reply received from The Hive:", response.statusCode, response.statusMessage, "\n", body)
}
resolve({
'status_code': response.statusCode,
'status_msg': response.statusMessage,
'body': body
});
}
} // handler function
); // request()
}); // Promise()
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment