'use strict';
// Function called to setup custom MarkdownIt rendering and parsing rules
var markdownItCustomPostInit = markdownItCustomPostInit
// Hint option passed to the CodeMirror constructor
var cmCustomHints = hintMISPElements
// Setup function called after the CodeMirror initialization
var cmCustomSetup = cmCustomSetup
// Hook allowing to alter the raw text before returning the GFM version to the user to be downloaded
var markdownGFMSubstitution = replaceMISPElementByTheirValue
// Post rendering hook called after the markdown is displayed, allowing to register listener
var markdownCustomPostRenderingListener = setupMISPElementMarkdownListeners
// Post rendering hook called after the markdown is display, allowing to perform any actions on the rendered markdown
var markdownCustomPostRenderingActions = attachRemoteMISPElements
// CodeMirror replacement/insertion actions that can be executed on the editor's text
var customReplacementActions = MISPElementReplacementActions
// Called after CodeMirror initialization to insert custom top bar buttons
var insertCustomToolbarButtons = insertMISPElementToolbarButtons
// Key of the model used by the form when saving
var modelNameForSave = 'EventReport';
// Key of the field used by the form when saving
var markdownModelFieldNameForSave = 'content';
var dotTemplateAttribute = doT.template("{{=it.type}}{{=it.value}}");
var dotTemplateAttributePicture = doT.template("
");
var dotTemplateGalaxyMatrix = doT.template("");
var dotTemplateTag = doT.template("{{=it.elementid}}");
var dotTemplateObject = doT.template("{{=it.type}}{{=it.value}}");
var dotTemplateObjectAttribute = doT.template("{{=it.objectname}}↦ {{=it.type}}{{=it.value}}");
var dotTemplateInvalid = doT.template("{{=it.scope}} ({{=it.id}})");
var dotCloseButtonTemplate = doT.template('');
var dotTemplateRenderingDisabled = doT.template("{{=it.value}}");
var dotTemplateSuggestionAttribute = doT.template("{{=it.type}}{{=it.value}}");
var renderingRules = {
'attribute': true,
'attribute-picture': true,
'object': true,
'object-attribute': true,
'tag': true,
'galaxymatrix': true,
'suggestion': true,
}
var galaxyMatrixTimer, tagTimers = {};
var cache_matrix = {}, cache_tag = {};
var firstCustomPostRenderCall = true;
var contentBeforeSuggestions
var typeToCategoryMapping
var entitiesFromComplexTool
var $suggestionContainer
var unreferencedElements = {
values: null,
context: null
};
var suggestionIDs = []
var suggestions = {}
var pickedSuggestion = { tableID: null, tr: null, entity: null, index: null, isContext: null }
/**
_____ _ __ __ _
/ ____| | | | \/ (_)
| | ___ __| | ___| \ / |_ _ __ _ __ ___ _ __
| | / _ \ / _` |/ _ \ |\/| | | '__| '__/ _ \| '__|
| |___| (_) | (_| | __/ | | | | | | | | (_) | |
\_____\___/ \__,_|\___|_| |_|_|_| |_| \___/|_|
*/
function cmCustomSetup() {
$suggestionContainer = $('').attr('id', 'suggestion-container').addClass('hidden')
$suggestionContainer.insertAfter('#editor-subcontainer')
}
/* Replacement actions and Toolbar addition */
function MISPElementReplacementActions(action) {
var start = cm.getCursor('start')
var end = cm.getCursor('end')
var content = cm.getRange(start, end)
var replacement = content
var setCursorTo = false
var noMatch = false
switch (action) {
case 'element':
replacement = '@[MISPElement]()'
end = null
cm.replaceRange(replacement, start)
cm.setSelection({line: start.line, ch: start.ch + 2}, {line: start.line, ch: start.ch + 2 + 11})
cm.focus()
return true;
case 'attribute':
replacement = '@[attribute]()'
end = null
setCursorTo = {line: start.line, ch: start.ch + replacement.length - 1}
break;
case 'attribute-attachment':
replacement = '@![attribute]()'
end = null
setCursorTo = {line: start.line, ch: start.ch + replacement.length - 1}
break;
case 'object':
replacement = '@[object]()'
end = null
setCursorTo = {line: start.line, ch: start.ch + replacement.length - 1}
break;
case 'tag':
replacement = '@[tag]()'
end = null
setCursorTo = {line: start.line, ch: start.ch + replacement.length - 1}
break;
case 'galaxy-matrix':
replacement = '@[galaxymatrix]()'
end = null
setCursorTo = {line: start.line, ch: start.ch + replacement.length - 1}
break;
default:
noMatch = true;
break;
}
if (noMatch) {
return false
}
cm.replaceRange(replacement, start, end)
if (setCursorTo !== false) {
cm.setCursor(setCursorTo.line, setCursorTo.ch)
}
cm.focus()
return true
}
function insertMISPElementToolbarButtons() {
insertTopToolbarSection()
insertTopToolbarButton('cube', 'attribute', 'Attribute')
insertTopToolbarButton('cubes', 'object', 'Object')
insertTopToolbarButton('image', 'attribute-attachment', 'Attribute picture')
insertTopToolbarButton('tag', 'tag', 'Tag')
insertTopToolbarButton('atlas', 'galaxy-matrix', 'Galaxy matrix')
}
/* Hints */
var MISPElementHints = {}
function buildMISPElementHints() {
MISPElementHints['attribute'] = []
Object.keys(proxyMISPElements['attribute']).forEach(function(uuid) {
var attribute = proxyMISPElements['attribute'][uuid]
MISPElementHints['attribute'].push(
[attribute.value, uuid],
[attribute.type, uuid],
[attribute.id, uuid],
[attribute.uuid, uuid],
)
})
MISPElementHints['object'] = []
Object.keys(proxyMISPElements['object']).forEach(function(uuid) {
var object = proxyMISPElements['object'][uuid]
var topPriorityValue = getTopPriorityValue(object)
MISPElementHints['object'].push(
[object.name, uuid],
[object.id, uuid],
[object.uuid, uuid],
[topPriorityValue, uuid],
)
})
MISPElementHints['galaxymatrix'] = []
Object.keys(proxyMISPElements['galaxymatrix']).forEach(function(uuid) {
var galaxy = proxyMISPElements['galaxymatrix'][uuid]
MISPElementHints['galaxymatrix'].push(
[galaxy.id, uuid],
[galaxy.uuid, uuid],
[galaxy.name, uuid],
[galaxy.namespace, uuid],
[galaxy.type, uuid],
)
})
MISPElementHints['tag'] = []
Object.keys(proxyMISPElements['tagname']).forEach(function(tagName) {
var tag = proxyMISPElements['tagname'][tagName]
MISPElementHints['tag'].push([tagName, tagName])
})
}
function hintMISPElements(cm, options) {
var authorizedMISPElements = ['attribute', 'object', 'galaxymatrix', 'tag']
var availableScopes = ['attribute', 'object', 'galaxymatrix', 'tag']
var reMISPElement = RegExp('@\\[(?' + authorizedMISPElements.join('|') + ')\\]\\((?[^\\)]+)?\\)');
var reMISPScope = RegExp('@\\[(?\\S+)\\]\\(\\)');
var reExtendedWord = /\S/
var hintList = []
var scope, elementID, element
var cursor = cm.getCursor()
var line = cm.getLine(cursor.line)
var start = cursor.ch
var end = cursor.ch
while (start && reExtendedWord.test(line.charAt(start - 1))) --start
while (end < line.length && reExtendedWord.test(line.charAt(end))) ++end
var word = line.slice(start, end).toLowerCase()
if (word === '@[]()') {
availableScopes.forEach(function(scope) {
hintList.push({
text: '@[' + scope + ']()'
})
});
return {
list: hintList,
from: CodeMirror.Pos(cursor.line, start),
to: CodeMirror.Pos(cursor.line, end)
}
}
var resScope = reMISPScope.exec(word)
if (resScope !== null) {
var partialScope = resScope.groups.scope
availableScopes.forEach(function(scope) {
if (scope.startsWith(partialScope) && scope !== partialScope) {
hintList.push({
text: '@[' + scope + ']()'
})
}
});
if (hintList.length > 0) {
return {
list: hintList,
from: CodeMirror.Pos(cursor.line, start),
to: CodeMirror.Pos(cursor.line, end)
}
}
}
var res = reMISPElement.exec(word)
if (res !== null) {
scope = res.groups.scope
elementID = res.groups.elementid !== undefined ? res.groups.elementid : ''
if (scope === 'tag') {
element = proxyMISPElements['tagname'][elementID]
} else {
element = proxyMISPElements[scope][elementID]
}
if (element !== undefined) {
hintList.push(
{
text: '@[' + scope + '](' + element.uuid + ')',
render: function(elem, self, data) {
var hintElement = renderHintElement(scope, element)
$(elem).append(hintElement)
},
className: 'hint-container',
}
)
} else { // search in hint arrays
var addedItems = {}
var maxHints = 20 + 10*(elementID.length - 3 >= 0 ? elementID.length - 3 : 0); // adapt hint numbers if typed value is large enough
if (MISPElementHints[scope] !== undefined) {
for (var i = 0; i < MISPElementHints[scope].length; i++) {
var hintArray = MISPElementHints[scope][i];
var hintValue = hintArray[0]
var hintUUID = hintArray[1]
if (hintList.length >= maxHints) {
break
}
if (hintValue.includes(elementID)) {
if (addedItems[hintUUID] === undefined) {
if (scope === 'tag') {
element = proxyMISPElements['tagname'][hintUUID]
element.uuid = hintUUID
} else {
element = proxyMISPElements[scope][hintUUID]
}
if (element !== undefined) {
hintList.push({
text: '@[' + scope + '](' + element.uuid + ')',
element: element,
render: function(elem, self, data) {
var hintElement = renderHintElement(scope, data.element)
$(elem).append(hintElement)
},
className: 'hint-container',
})
}
addedItems[hintUUID] = true
}
}
}
}
}
return {
list: hintList,
from: CodeMirror.Pos(cursor.line, start),
to: CodeMirror.Pos(cursor.line, end)
}
}
return null
}
function renderHintElement(scope, element) {
var $node;
if (scope == 'attribute') {
$node = $('').addClass('hint-attribute')
if (isValidObjectAttribute(element)) {
$node.append($('').addClass('fas fa-cubes').css('margin-right', '3px'))
}
$node.append($('').addClass('').text('[' + element.category + '] '))
.append($('').addClass('bold').text(element.type + ' '))
.append(
$('').addClass('bold blue ellipsis-overflow')
.css({
'max-width': '500px',
'display': 'table-cell'
})
.text(element.value)
)
} else if (scope == 'object') {
var topPriorityValue = getTopPriorityValue(element)
$node = $('').addClass('hint-object')
$node.append($('').addClass('').text('[' + element['meta-category'] + '] '))
.append($('').addClass('bold').text(element.name + ' '))
.append(
$('').addClass('bold blue ellipsis-overflow')
.css({
'max-width': '500px',
'display': 'table-cell'
})
.text(topPriorityValue)
)
} else if (scope == 'galaxymatrix') {
$node = $('').addClass('hint-galaxymatrix')
$node.append($('').addClass('').text('[' + element.namespace + '] '))
.append($('').addClass('bold').text(element.type + ' '))
.append($('').addClass('bold blue').text(element.name))
} else if (scope == 'tag') {
$node = $('').addClass('hint-tag')
$node.append(constructTagHtml(element.name, element.colour, {'box-shadow': 'none'}))
} else {
$node = $('No match') // should not happen
}
return $node
}
/**
__ __ _ _ _____ _
| \/ | | | | | |_ _| |
| \ / | __ _ _ __| | ____| | _____ ___ __ | | | |_
| |\/| |/ _` | '__| |/ / _` |/ _ \ \ /\ / / '_ \ | | | __|
| | | | (_| | | | < (_| | (_) \ V V /| | | |_| |_| |_
|_| |_|\__,_|_| |_|\_\__,_|\___/ \_/\_/ |_| |_|_____|\__|
*/
function markdownItCustomPostInit() {
markdownItSetupRules()
fetchProxyMISPElements(function() {
doRender()
})
}
function markdownItSetupRules() {
md.renderer.rules.MISPElement = MISPElementRenderer;
md.renderer.rules.MISPPictureElement = MISPPictureElementRenderer;
md.inline.ruler.push('MISP_element_rule', MISPElementRule);
md.core.ruler.push('MISP_element_suggestion_rule', MISPElementSuggestionRule);
}
function MISPElementSuggestionRule(state) {
var blockTokens = state.tokens
var tokens, blockToken, currentToken
var indexOfAllLines, lineOffset, absoluteLine, relativeIndex
var tokenMap
var i, j, l
for (i = 0, l = blockTokens.length; i < l; i++) {
blockToken = blockTokens[i]
if (blockToken.type !== 'inline') {
continue
}
tokens = blockToken.children;
for (j = 0; j < tokens.length; j++) {
currentToken = tokens[j];
if (currentToken.type !== 'MISPElement' && !currentToken.isSuggestion) {
continue
}
if (blockToken.indexOfAllLines === undefined) {
indexOfAllLines = new md.block.State(blockToken.content, md, {}, [])
blockToken.indexOfAllLines = indexOfAllLines
}
lineOffset = getLineNumInArrayList(currentToken.content.indexes.start, blockToken.indexOfAllLines.bMarks)
tokenMap = findBackClosestStartLine(blockTokens, i)
var absoluteLine = tokenMap[0] + lineOffset
var relativeIndex = currentToken.content.indexes.start - blockToken.indexOfAllLines.bMarks[lineOffset]
state.tokens[i].children[j].content.indexes.lineStart = absoluteLine
state.tokens[i].children[j].content.indexes.start = relativeIndex
}
}
}
/* Parsing Rules */
function MISPElementRule(state, startLine, endLine, silent) {
var pos, start, labelStart, labelEnd, res, elementID, code, content, token, tokens, attrs, scope
var oldPos = state.pos,
max = state.posMax
if (state.src.charCodeAt(state.pos) !== 0x40/* @ */) { return false; }
if (state.src.charCodeAt(state.pos + 1) === 0x21/* ! */) {
if (state.src.charCodeAt(state.pos + 2) !== 0x5B/* [ */) { return false;}
} else {
if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; }
}
var isPicture = state.src.charCodeAt(state.pos + 1) === 0x21/* ! */
if (isPicture) {
labelStart = state.pos + 3;
labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 2, false);
} else {
labelStart = state.pos + 2;
labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false);
}
// parser failed to find ']', so it's not a valid link
if (labelEnd < 0) { return false; }
scope = state.src.slice(labelStart, labelEnd)
pos = labelEnd + 1;
if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
start = pos;
if (scope == 'tag' || scope == 'suggestion') { // tags may contain spaces
res = parseDestinationValue(state.src, pos, state.posMax);
} else {
res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax);
}
if (res.ok) {
// parseLinkDestination does not support trailing characters such as `.` after the link
// so we have to find the matching `)`
var destinationEnd = res.str.length - 1
var traillingCharNumber = 0
for (var i = res.str.length-1; i > 1; i--) {
var code = res.str.charCodeAt(i)
if (code === 0x29 /* ) */) {
destinationEnd = i
break
}
traillingCharNumber++
}
elementID = res.str.substring(1, destinationEnd);
pos = res.pos - 1 - traillingCharNumber;
}
}
if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {
state.pos = oldPos;
return false;
}
pos++;
if (scope == 'tag' || scope == 'suggestion') {
var reTagName = /^[^\n)]+$/
if (!reTagName.test(elementID)) {
return false;
}
} else {
var reUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/
if (!reUUID.test(elementID)) {
return false;
}
}
// We found the end of the link, and know for a fact it's a valid link;
// so all that's left to do is to call tokenizer.
content = {
scope: scope,
elementID: elementID,
indexes: {
start: oldPos,
}
}
if (isPicture) {
token = state.push('MISPPictureElement', 'div', 0);
} else {
token = state.push('MISPElement', 'div', 0);
if (scope == 'suggestion') {
token.isSuggestion = true
content.indexes.suggestionID = consumeSuggestionID()
} else {
token.isSuggestion = false
}
}
token.children = tokens;
token.content = content;
state.pos = pos;
state.posMax = max;
return true;
}
/* Rendering rules */
function MISPElementRenderer(tokens, idx, options, env, slf) {
var allowedScope = ['attribute', 'object', 'galaxymatrix', 'tag', 'suggestion']
var token = tokens[idx];
var scope = token.content.scope
var elementID = token.content.elementID
var indexes = token.content.indexes
if (allowedScope.indexOf(scope) == -1) {
return renderInvalidMISPElement(scope, elementID);
}
return renderMISPElement(scope, elementID, indexes)
}
function MISPPictureElementRenderer(tokens, idx, options, env, slf) {
var allowedScope = ['attribute']
var token = tokens[idx];
var scope = token.content.scope
var elementID = token.content.elementID
if (allowedScope.indexOf(scope) == -1) {
return renderInvalidMISPElement(scope, elementID);
}
return renderMISPPictureElement(scope, elementID)
}
function renderMISPElement(scope, elementID, indexes) {
var templateVariables
if (scope == 'suggestion') {
var suggestionKey = 'suggestion-' + String(indexes.suggestionID)
if (suggestions[elementID] !== undefined) {
var suggestion = suggestions[elementID][suggestionKey]
if (suggestion !== undefined) {
templateVariables = sanitizeObject({
scope: 'suggestion',
elementid: elementID,
eventid: eventid,
type: suggestion.complexTypeToolResult.picked_type,
origValue: elementID,
value: suggestion.complexTypeToolResult.value,
indexStart: indexes.start,
suggestionkey: suggestionKey,
checked: suggestion.checked
})
return renderTemplateBasedOnRenderingOptions(scope, dotTemplateSuggestionAttribute, templateVariables);
}
}
}
if (proxyMISPElements !== null) {
if (scope == 'attribute') {
var attribute = proxyMISPElements[scope][elementID]
if (attribute !== undefined) {
var templateToRender = dotTemplateAttribute
var attributeData = {
scope: 'attribute',
elementid: elementID,
type: attribute.type,
value: attribute.value
}
if (isValidObjectAttribute(attribute)) {
var mispObject = getObjectFromAttribute(attribute)
if (mispObject !== undefined) {
attributeData.type = attribute.object_relation
attributeData.objectname = mispObject.name
templateToRender = dotTemplateObjectAttribute
}
}
templateVariables = sanitizeObject(attributeData)
return renderTemplateBasedOnRenderingOptions(scope, templateToRender, templateVariables);
}
} else if (scope == 'object') {
var mispObject = proxyMISPElements[scope][elementID]
if (mispObject !== undefined) {
var associatedTemplate = mispObject.template_uuid + '.' + mispObject.template_version
var objectTemplate = proxyMISPElements['objectTemplates'][associatedTemplate]
var topPriorityValue = mispObject.Attribute.length
if (objectTemplate !== undefined) {
var temp = getPriorityValue(mispObject, objectTemplate)
topPriorityValue = temp !== false ? temp : topPriorityValue
}
templateVariables = sanitizeObject({
scope: 'object',
elementid: elementID,
type: mispObject.name,
value: topPriorityValue
})
return renderTemplateBasedOnRenderingOptions(scope, dotTemplateObject, templateVariables);
}
} else if (scope == 'tag') {
templateVariables = sanitizeObject({scope: 'tag', elementid: elementID, eventid: eventid, value: elementID})
return renderTemplateBasedOnRenderingOptions(scope, dotTemplateTag, templateVariables);
} else if (scope == 'galaxymatrix') {
templateVariables = sanitizeObject({scope: 'galaxymatrix', elementid: elementID, eventid: eventid, value: elementID})
return renderTemplateBasedOnRenderingOptions(scope, dotTemplateGalaxyMatrix, templateVariables);
}
}
return renderInvalidMISPElement(scope, elementID)
}
function renderMISPPictureElement(scope, elementID) {
if (proxyMISPElements !== null) {
var attribute = proxyMISPElements[scope][elementID]
if (attribute !== undefined) {
var templateVariables = sanitizeObject({
scope: 'attribute',
elementid: elementID,
type: attribute.type,
value: attribute.value,
alt: scope + ' ' + elementID,
src: baseurl + '/attributes/viewPicture/' + attribute.id,
title: attribute.type + ' ' + attribute.value,
})
return renderTemplateBasedOnRenderingOptions('attribute-picture', dotTemplateAttributePicture, templateVariables);
}
}
return renderInvalidMISPElement(scope, elementID)
}
function renderInvalidMISPElement(scope, elementID) {
var templateVariables = sanitizeObject({
scope: invalidMessage,
id: elementID
})
return dotTemplateInvalid(templateVariables);
}
function renderTemplateBasedOnRenderingOptions(scope, templateToRender, templateVariables) {
if (renderingRules[scope]) {
return templateToRender(templateVariables)
} else {
return dotTemplateRenderingDisabled(templateVariables)
}
}
function setupMISPElementMarkdownListeners() {
var $elements = $('.misp-element-wrapper.attribute:not(".suggestion"), .misp-element-wrapper.object, .misp-picture-wrapper > img, .embeddedTag');
$elements.popover({
trigger: 'click',
html: true,
container: isInsideModal() ? 'body' : '#viewer-container',
placement: 'top',
title: getTitleFromMISPElementDOM,
content: getContentFromMISPElementDOM
})
setupSuggestionMarkdownListeners()
}
function setupSuggestionMarkdownListeners() {
$('.misp-element-wrapper').filter('.suggestion').click(function(e) {
var $checkbox = $(this).find('input[type="checkbox"]')
$checkbox.prop('checked', !$checkbox.prop('checked'))
updateSuggestionCheckedState($(this), $checkbox)
}).find('input[type="checkbox"]')
.click(function(e) {
e.stopPropagation()
})
.change(function() {
updateSuggestionCheckedState($(this).closest('.suggestion'), $(this))
})
}
function updateSuggestionCheckedState($wrapper, $checkbox) {
var elementID = $wrapper.data('elementid')
var suggestionKey = $wrapper.data('suggestionkey')
suggestions[elementID][suggestionKey].checked = $checkbox.prop('checked')
}
function attachRemoteMISPElements() {
$('.embeddedGalaxyMatrix[data-scope="galaxymatrix"]').each(function() {
var $div = $(this)
clearTimeout(galaxyMatrixTimer);
$div.append($('').css('font-size', '24px').append(loadingSpanAnimation))
var eventID = $div.data('eventid')
var elementID = $div.data('elementid')
var cacheKey = eventid + '-' + elementID
if (cache_matrix[cacheKey] === undefined) {
galaxyMatrixTimer = setTimeout(function() {
attachGalaxyMatrix($div, eventID, elementID)
}, firstCustomPostRenderCall ? 0 : slowDebounceDelay);
} else {
$div.html(cache_matrix[cacheKey])
}
})
var tagNamesToLoad = [];
var tagsLoading = [];
$('.embeddedTag[data-scope="tag"]').each(function() {
var $div = $(this);
var elementID = $div.data('elementid');
if (!(elementID in cache_tag)) {
$div.append($('').append(loadingSpanAnimation));
tagNamesToLoad.push(elementID);
tagsLoading.push($div);
} else {
$div.html(cache_tag[elementID]);
}
}).promise().done(function() {
if (tagNamesToLoad.length === 0) {
return;
}
fetchTagInfo(tagNamesToLoad, function() {
$.each(tagsLoading, function() {
var $div = $(this);
var elementID = $div.data('elementid');
if (elementID in cache_tag) {
$div.html(cache_tag[elementID]);
}
});
});
});
if (firstCustomPostRenderCall) {
// Wait, because .each calls are asynchronous
setTimeout(function() {
firstCustomPostRenderCall = false;
}, 1000)
}
}
function attachGalaxyMatrix($elem, eventid, elementID) {
var galaxy = proxyMISPElements['galaxymatrix'][elementID]
if (galaxy === undefined) {
console.log('Something wrong happened. Could not fetch galaxy from proxy')
return
}
var galaxyType = galaxy.type
$.ajax({
data: {
"returnFormat": "attack",
"eventid": eventid,
"attackGalaxy": galaxyType
},
success:function(data, textStatus) {
$elem.empty().append($(data))
$elem.find('#attackmatrix_div').css({
'max-width': 'unset',
'min-width': 'unset',
'width': 'calc(100% - 5px)'
})
$elem.find('#checkbox_attackMatrix_showAll').click()
$elem.find('#attackmatrix_div .heatCell').each(function() {
if ($(this).css('background-color').length > 0 && $(this).css('background-color') != 'rgba(0, 0, 0, 0)') {
$(this).attr('style', 'background-color:' + $(this).css('background-color') + ' !important; color:' + $(this).css('color') + ' !important;');
}
})
var cacheKey = eventid + '-' + elementID
cache_matrix[cacheKey] = $elem.find('#attackmatrix_div')[0].outerHTML;
},
error: function(jqXHR, textStatus, errorThrown) {
var templateVariables = sanitizeObject({
scope: 'Error while fetching matrix',
id: graphID
})
var placeholder = dotTemplateInvalid(templateVariables)
$elem.empty()
.css({'text-align': 'center'})
.append($(placeholder))
},
type:"post",
url: baseurl + "/events/restSearch"
})
}
function fetchTagInfo(tagNames, callback) {
$.ajax({
data: {
"tag": tagNames,
},
success: function (data) {
var $tag, tagName;
data = $.parseJSON(data)
for (var i = 0; i < data.length; i++) {
var tag = data[i];
tagName = tag.Tag.name;
proxyMISPElements['tag'][tagName] = tag;
$tag = getTagReprensentation(tag);
cache_tag[tagName] = $tag[0].outerHTML;
}
// If tag name doesn't exists, construct empty placeholder
for (i = 0; i < tagNames.length; i++) {
tagName = tagNames[i];
if (!(tagName in cache_tag)) {
$tag = constructTagHtml(tagName, '#ffffff', {'border': '1px solid #000'});
cache_tag[tagName] = $tag[0].outerHTML;
}
}
},
error: function (jqXHR, textStatus, errorThrown) {
// Query failed, fill cache with placeholder
var tagName, templateVariables;
for (var i = 0; i < tagNames.length; i++) {
tagName = tagNames[i];
if (!(tagName in cache_tag)) {
templateVariables = sanitizeObject({
scope: 'Error while fetching tag',
id: tagName
});
cache_tag[tagName] = dotTemplateInvalid(templateVariables);
}
}
},
complete: function () {
if (callback !== undefined) {
callback()
}
},
type: "post",
url: baseurl + "/tags/search/0/1/0"
})
}
/**
_____ _
/ ____| (_)
| (___ __ ___ ___ _ __ __ _
\___ \ / _` \ \ / / | '_ \ / _` |
____) | (_| |\ V /| | | | | (_| |
|_____/ \__,_| \_/ |_|_| |_|\__, |
__/ |
|___/
*/
function replaceMISPElementByTheirValue(raw) {
var match, replacement, element
var final = ''
var authorizedMISPElements = ['attribute', 'object']
var reMISPElement = RegExp('@\\[(?' + authorizedMISPElements.join('|') + ')\\]\\((?[\\d]+)\\)', 'g');
var offset = 0
while ((match = reMISPElement.exec(raw)) !== null) {
element = proxyMISPElements[match.groups.scope][match.groups.elementid]
if (element !== undefined) {
replacement = match.groups.scope + '-' + element.uuid
} else {
replacement = match.groups.scope + '-' + match.groups.elementid
}
final += raw.substring(offset, match.index) + replacement
offset = reMISPElement.lastIndex
}
final += raw.substring(offset)
return final
}
/**
__ __
| \/ |
| \ / | ___ _ __ _ _
| |\/| |/ _ \ '_ \| | | |
| | | | __/ | | | |_| |
|_| |_|\___|_| |_|\__,_|
*/
function injectCustomRulesMenu() {
var $MISPElementMenuItem = createRulesMenuItem('MISP Elements', $(''), 'parser', 'MISP_element_rule')
$markdownDropdownRulesMenu.append($MISPElementMenuItem)
createSubMenu({
name: 'Markdown rendering rules',
icon: 'fab fa-markdown',
items: [
{ name: 'Attribute', icon: 'fas fa-cube', ruleScope: 'render', ruleName: 'attribute', isToggleableRule: true },
{ name: 'Attribute picture', icon: 'fas fa-image', ruleScope: 'render', ruleName: 'attribute-picture', isToggleableRule: true },
{ name: 'Object', icon: 'fas fa-cubes', ruleScope: 'render', ruleName: 'object', isToggleableRule: true },
{ name: 'Object Attribute', icon: 'fas fa-cube', ruleScope: 'render', ruleName: 'object-attribute', isToggleableRule: true },
{ name: 'Tag', icon: 'fas fa-tag', ruleScope: 'render', ruleName: 'tag', isToggleableRule: true },
{ name: 'Galaxy matrix', icon: 'fas fa-atlas', ruleScope: 'render', ruleName: 'galaxymatrix', isToggleableRule: true },
{ name: 'Suggestion', icon: 'fas fa-magic', ruleScope: 'render', ruleName: 'suggestion', isToggleableRule: true },
]
})
createSubMenu({
name: 'Extract entities',
icon: 'fas fa-cogs',
items: [
{ name: 'Manual extraction', icon: 'fas fa-highlighter', clickHandler: manualEntitiesExtraction},
{ name: 'Automatic extraction', icon: 'fas fa-magic', clickHandler: automaticEntitiesExtraction},
]
})
reloadRenderingRuleEnabledUI()
}
function markdownItToggleCustomRule(rulename, event) {
var enabled
if (rulename == 'MISP_element_rule') {
var rule = getRuleStatus('inline', 'ruler', 'MISP_element_rule')
if (rule !== false) {
enabled = rule.enabled
}
}
return {
found: enabled !== undefined,
enabled: enabled
}
}
function markdownItToggleRenderingRule(rulename, event) {
if (event !== undefined) {
event.stopPropagation()
}
if (renderingRules[rulename] === undefined) {
console.log('Rule does not exist')
return
}
renderingRules[rulename] = !renderingRules[rulename]
doRender()
reloadRenderingRuleEnabledUI()
}
function reloadRenderingRuleEnabledUI() {
Object.keys(renderingRules).forEach(function(rulename) {
var enabled = renderingRules[rulename]
if (enabled) {
$('#markdownrendering-' + rulename + '-rendering-enabled').show()
$('#markdownrendering-' + rulename + '-rendering-disabled').hide()
} else {
$('#markdownrendering-' + rulename + '-rendering-enabled').hide()
$('#markdownrendering-' + rulename + '-rendering-disabled').show()
}
})
}
/**
_____ _ _
/ ____| | | (_)
| (___ _ _ __ _ __ _ ___ ___| |_ _ ___ _ __
\___ \| | | |/ _` |/ _` |/ _ \/ __| __| |/ _ \| '_ \
____) | |_| | (_| | (_| | __/\__ \ |_| | (_) | | | |
|_____/ \__,_|\__, |\__, |\___||___/\__|_|\___/|_| |_|
__/ | __/ |
|___/ |___/
*/
function automaticEntitiesExtraction() {
var url = baseurl + '/eventReports/extractAllFromReport/' + reportid
openGenericModal(url)
}
function manualEntitiesExtraction() {
contentBeforeSuggestions = getEditorData()
pickedSuggestion = { tableID: null, tr: null, entity: null, index: null, isContext: null }
extractFromReport(function(data) {
typeToCategoryMapping = data.typeToCategoryMapping
prepareSuggestionInterface(data.complexTypeToolResult, data.replacementValues, data.replacementContext)
toggleSuggestionInterface(true)
})
}
function prepareSuggestionInterface(complexTypeToolResult, replacementValues, replacementContext) {
toggleMarkdownEditorLoading(true, 'Processing document')
entitiesFromComplexTool = complexTypeToolResult
searchForUnreferencedValues(replacementValues)
searchForUnreferencedContext(replacementContext)
entitiesFromComplexTool = injectNumberOfOccurrencesInReport(entitiesFromComplexTool)
setupSuggestionMarkdownListeners()
constructSuggestionTables(entitiesFromComplexTool)
toggleMarkdownEditorLoading(false)
}
function highlightPickedSuggestionInReport() {
setEditorData(contentBeforeSuggestions)
resetSuggestionIDs()
for (var i = 0; i < entitiesFromComplexTool.length; i++) {
var entity = entitiesFromComplexTool[i];
if (pickedSuggestion.entity.value == entity.value) {
var converted = convertEntityIntoSuggestion(contentBeforeSuggestions, entity)
setEditorData(converted)
var indicesInCM = getAllSuggestionIndicesOf(converted, entity.value, false)
constructSuggestionMapping(entity, indicesInCM)
break
}
}
}
function highlightPickedReplacementInReport() {
var entity = pickedSuggestion.entity
setEditorData(contentBeforeSuggestions)
var content = contentBeforeSuggestions
resetSuggestionIDs()
var converted = convertEntityIntoSuggestion(content, entity)
setEditorData(converted)
var indicesInCM = getAllSuggestionIndicesOf(converted, entity.value, false)
constructSuggestionMapping(entity, indicesInCM)
}
function convertEntityIntoSuggestion(content, entity) {
var converted = ''
var entityValue;
if (entity.importRegexMatch) {
entityValue = entity.importRegexMatch;
} else if (entity.original_value) {
entityValue = entity.original_value;
} else {
entityValue = entity.value;
}
var splittedContent = content.split(entityValue)
splittedContent.forEach(function(text, i) {
converted += text
if (i < splittedContent.length-1) {
if (isDoubleExtraction(converted)) {
converted += entity.value
} else {
converted += '@[suggestion](' + entity.value + ')'
}
}
})
return converted
}
function extractFromReport(callback) {
$.ajax({
dataType: "json",
beforeSend: function() {
toggleMarkdownEditorLoading(true, 'Extracting entities')
},
success:function(data, textStatus) {
callback(data);
},
error: function(jqXHR, textStatus, errorThrown) {
showMessage('fail', 'Could not extract entities from report. ' + textStatus)
},
complete: function() {
toggleMarkdownEditorLoading(false)
},
type:'get',
url: baseurl + '/eventReports/extractFromReport/' + reportid
})
}
function constructSuggestionMapping(entity, indicesInCM) {
var suggestionBaseKey = 'suggestion-', suggestionKey
suggestions[entity.value] = {}
indicesInCM.forEach(function(index) {
suggestionKey = suggestionBaseKey + getNewSuggestionID()
suggestions[entity.value][suggestionKey] = {
startIndex: index,
endIndex: {index: index.index + entity.value.length},
complexTypeToolResult: entity,
checked: true
}
});
setTimeout(function() {
var notRenderedCount = suggestionIDs.length
if (notRenderedCount > 0) {
pickedSuggestion.tr.find('.occurrence-issues')
.attr('title', 'Could not render ' + notRenderedCount + ' elements. Manual investigation required')
.text('⚠ ' + notRenderedCount)
}
}, 300);
}
function injectNumberOfOccurrencesInReport(entities) {
var content = getEditorData()
entities.forEach(function(entity, i) {
entities[i].occurrences = getAllIndicesOf(content, entity.original_value, false, false).length
})
return entities
}
function getAllSuggestionIndicesOf(content, entity, caseSensitive) {
var toSearch = '@[suggestion](' + entity + ')'
return getAllIndicesOf(content, toSearch, caseSensitive, true)
}
function toggleSuggestionInterface(enabled) {
if (enabled) {
setCMReadOnly(true)
setMode('splitscreen')
$('#editor-subcontainer').hide()
$suggestionContainer.show()
} else {
setCMReadOnly(false)
setEditorData(originalRaw)
$('#editor-subcontainer').show()
$suggestionContainer.hide()
$mardownViewerToolbar.find('.btn-group:first button').css('visibility', 'visible')
$('#suggestionCloseButton').remove()
cm.refresh()
}
}
function searchForUnreferencedValues(replacementValues) {
unreferencedElements.values = {}
var content = getEditorData()
Object.keys(replacementValues).forEach(function(attributeValue) {
var replacementValue = replacementValues[attributeValue]
var indices = getAllIndicesOf(content, replacementValue.valueInReport, true, true)
if (indices.length > 0) {
var attributes = [];
Object.keys(proxyMISPElements['attribute']).forEach(function(uuid) {
if (replacementValue.attributeUUIDs.indexOf(uuid) > -1) {
attributes.push(proxyMISPElements['attribute'][uuid])
}
});
unreferencedElements.values[replacementValue.valueInReport] = {
attributes: attributes,
indices: indices
}
if (attributeValue != replacementValue.valueInReport) {
unreferencedElements.values[replacementValue.valueInReport].importRegexMatch = attributeValue
}
}
})
}
function searchForUnreferencedContext(replacementContext) {
unreferencedElements.context = {}
var content = getEditorData()
Object.keys(replacementContext).forEach(function(rawText) {
var indices = getAllIndicesOf(content, rawText, true, true)
if (indices.length > 0) {
replacementContext[rawText].indices = indices
}
})
unreferencedElements.context = replacementContext;
}
function pickSuggestionColumn(index, tableID, force) {
tableID = tableID === undefined ? 'replacementTable' : tableID
force = force === undefined ? false : force;
if (pickedSuggestion.tableID != tableID || pickedSuggestion.index != index || force) {
if (pickedSuggestion.tr) {
pickedSuggestion.tr.find('.occurrence-issues').attr('title', '').text('')
}
var $trs = $('#' + tableID + ' tr')
$trs.removeClass('info').find('button').prop('disabled', true)
$trs.find('select').prop('disabled', true)
var $tr = $('#' + tableID + ' tr[data-entityindex="' + index + '"]')
if ($tr.length > 0) {
$tr.addClass('info').find('button').prop('disabled', false)
$tr.find('select').prop('disabled', false)
pickedSuggestion = {
tableID: tableID,
tr: $tr,
index: index
}
if (tableID === 'replacementTable') {
var uuid = $tr.find('select.attribute-replacement').val()
pickedSuggestion['entity'] = {
value: $tr.data('attributeValue'),
picked_type: proxyMISPElements['attribute'][uuid].type,
replacement: uuid
}
if (proxyMISPElements['attribute'][uuid].importRegexValue) {
pickedSuggestion['entity']['importRegexMatch'] = proxyMISPElements['attribute'][uuid].importRegexValue
}
highlightPickedReplacementInReport()
} else if (tableID === 'contextReplacementTable') {
pickedSuggestion['entity'] = {
value: $tr.data('contextValue'),
picked_type: 'tag',
replacement: $tr.find('select.context-replacement').val()
}
pickedSuggestion['isContext'] = true
highlightPickedReplacementInReport()
} else {
pickedSuggestion['entity'] = $tr.data('entity')
pickedSuggestion['entity']['picked_type'] = $tr.find('select.type').val()
highlightPickedSuggestionInReport()
}
}
}
}
function getContentWithCheckedElements(isReplacement) {
var value = pickedSuggestion.entity.value
var content = getEditorData()
var contentWithPickedSuggestions = ''
var nextIndex = 0
var suggestionLength = '@[suggestion]()'.length + pickedSuggestion.entity.value.length
Object.keys(suggestions[value]).forEach(function(suggestionKey, i) {
var suggestion = suggestions[value][suggestionKey]
contentWithPickedSuggestions += content.substr(nextIndex, suggestion.startIndex.index - nextIndex)
nextIndex = suggestion.startIndex.index
var renderedInMardown = $('.misp-element-wrapper.suggestion[data-suggestionkey="' + suggestionKey + '"]').length > 0;
if (suggestion.checked && renderedInMardown) { // If the suggestion is not rendered, ignore it (could happen if parent block is escaped)
if (isReplacement) {
if (pickedSuggestion.isContext === true) {
contentWithPickedSuggestions += '@[tag](' + suggestion.complexTypeToolResult.replacement + ')'
} else {
contentWithPickedSuggestions += '@[attribute](' + suggestion.complexTypeToolResult.replacement + ')'
}
} else {
contentWithPickedSuggestions += content.substr(nextIndex, suggestionLength)
}
} else {
contentWithPickedSuggestions += value
}
nextIndex += suggestionLength
})
contentWithPickedSuggestions += content.substr(nextIndex)
return contentWithPickedSuggestions
}
function getSuggestionMapping() {
var getSuggestionMapping = {}
var $select = pickedSuggestion.tr.find('select')
var entity = pickedSuggestion.entity
getSuggestionMapping[entity.value] = {
'type': $select.filter('.type').val(),
'category': $select.filter('.category').val(),
'to_ids': entity.to_ids
}
return getSuggestionMapping
}
function submitReplacement() {
var contentWithPickedReplacements = getContentWithCheckedElements(true)
setEditorData(contentWithPickedReplacements);
saveMarkdown(false, function() {
setEditorData(originalRaw)
manualEntitiesExtraction()
})
}
function submitExtractionSuggestion() {
var url = baseurl + '/eventReports/replaceSuggestionInReport/' + reportid
var contentWithPickedSuggestions = getContentWithCheckedElements(false)
var suggestionsMapping = getSuggestionMapping()
fetchFormDataAjax(url, function(formHTML) {
$('body').append($('').html(formHTML))
var $tmpForm = $('#temp form')
var formUrl = $tmpForm.attr('action')
$tmpForm.find('[name="data[EventReport][suggestions]"]').val(JSON.stringify({
'content': contentWithPickedSuggestions,
'mapping': suggestionsMapping
}))
$.ajax({
data: $tmpForm.serialize(),
beforeSend: function() {
toggleMarkdownEditorLoading(true, 'Applying suggestions in report')
},
success:function(postResult, textStatus) {
if (postResult) {
showMessage('success', postResult.message);
if (postResult.data !== undefined) {
var report = postResult.data.report.EventReport
var complexTypeToolResult = postResult.data.complexTypeToolResult
var replacementValues = postResult.data.replacementValues
var replacementContext = postResult.data.replacementContext
lastModified = report.timestamp + '000'
refreshLastUpdatedField()
originalRaw = report.content
revalidateContentCache()
fetchProxyMISPElements(function() {
setEditorData(originalRaw)
contentBeforeSuggestions = originalRaw
pickedSuggestion = { tableID: null, tr: null, entity: null, index: null, isContext: null }
pickSuggestionColumn(-1)
prepareSuggestionInterface(complexTypeToolResult, replacementValues, replacementContext)
})
}
}
},
error: function(jqXHR, textStatus, errorThrown) {
if (jqXHR.responseJSON) {
showMessage('fail', jqXHR.responseJSON.errors);
} else {
showMessage('fail', saveFailedMessage + ': ' + errorThrown);
}
},
complete:function() {
$('#temp').remove();
toggleMarkdownEditorLoading(false)
},
type:"post",
url: formUrl
})
})
}
/**
_ _ _ _ _
| | | | | (_) |
| | | | |_ _| |___
| | | | __| | / __|
| |__| | |_| | \__ \
\____/ \__|_|_|___/
*/
function fetchProxyMISPElements(callback) {
var url = baseurl + '/eventReports/getProxyMISPElements/' + reportid
var errorMessage = 'Could not fetch MISP Elements'
$.ajax({
dataType: "json",
url: url,
data: {},
beforeSend: function() {
toggleMarkdownEditorLoading(true, 'Loading MISP Elements')
},
success: function(data) {
if (data) {
proxyMISPElements = data
proxyMISPElements['tag'] = []
buildMISPElementHints()
} else {
showMessage('fail', errorMessage);
}
},
error: function (data, textStatus, errorThrown) {
showMessage('fail', errorMessage + '. ' + textStatus + ": " + errorThrown);
},
complete: function() {
toggleMarkdownEditorLoading(false)
callback()
}
})
}
function getElementFromDom(node) {
var scope = $(node).data('scope')
var elementID = $(node).data('elementid')
if (scope !== undefined && elementID !== undefined) {
return {
element: proxyMISPElements[scope][elementID],
scope: scope,
elementID: elementID
}
}
return false
}
function getTitleFromMISPElementDOM() {
var data = getElementFromDom(this)
return buildTitleForMISPElement(data)
}
function buildTitleForMISPElement(data) {
var title = invalidMessage
var dismissButton = ''
if (data !== false) {
var templateVariables = sanitizeObject(data)
dismissButton = dotCloseButtonTemplate(templateVariables)
title = data.scope.charAt(0).toUpperCase() + templateVariables.scope.slice(1) + ' ' + templateVariables.elementID
}
return title + dismissButton
}
function closeThePopover(closeButton) {
var scope = $(closeButton).data('scope')
var elementID = $(closeButton).data('elementid')
var $MISPElement = $('#viewer [data-scope="' + scope + '"][data-elementid="' + elementID.replaceAll('\"', '\\\"') + '"]')
if ($MISPElement.length > 0) {
$MISPElement.popover('hide');
} else {
$(closeButton).closest('.popover').remove()
}
}
function constructAttributeRow(attribute, fromObject) {
fromObject = fromObject !== undefined ? fromObject : false
var attributeFieldsToRender = ['id', 'category', 'type'].concat(fromObject ? ['object_relation'] : [], ['value', 'comment'])
var $tr = $('