Skip to content

Commit

Permalink
Mapex 81 restrict version selection (#27)
Browse files Browse the repository at this point in the history
* build base for generating website

* build landing page from template

* generate index page

* copy most recent verision pair into main index

* fix script to link to different version combo

* set default links

* fix linking for sphinx

* fix linking for sphinx x2

* switch to custom input dropdowns

* handle veris verison selection

* fix edge cases

* copy most recent verision pair into main index

* fix script to link to different version combo

* set default links

* fix linking for sphinx

* fix linking for sphinx x2

* switch to custom input dropdowns

* handle veris verison selection

* fix edge cases

* fix sphinx error

* fix build script

* fix last edge case

---------

Co-authored-by: arobbins <arobbins@mitre.org>
  • Loading branch information
allisonrobbins and allisonrobbins authored Dec 8, 2023
1 parent 523099e commit e9c35fb
Show file tree
Hide file tree
Showing 3 changed files with 348 additions and 31 deletions.
4 changes: 1 addition & 3 deletions src/mappings_explorer/site_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,11 @@ def build_external_landing(
("num_mappings", "Number of Mappings"),
]

project_id = project.id if project.id != "nist" else "nist_800_53"
stream = template.stream(
title=project.label + " Landing",
url_prefix=url_prefix,
control=project.label,
description=project.description,
project_id=project_id,
project_version=project_version,
versions=project.versions,
attack_version=attack_version,
Expand All @@ -272,6 +270,7 @@ def build_external_landing(
headers=headers,
group_headers=group_headers,
groups=project.groups,
valid_versions=project.validVersions,
)
stream.dump(str(output_path))
print(
Expand Down Expand Up @@ -358,7 +357,6 @@ def build_external_control(
group_id=group_id,
group_name=group_name,
project=project,
project_id=project.id,
description=project.description,
tableHeaders=project.tableHeaders,
control_version=project_version,
Expand Down
288 changes: 264 additions & 24 deletions src/mappings_explorer/templates/_set_versions.html.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{% block set_version %}
<section id="version-select" class="version-select">
<meta id="version-combos" data-versions="{{valid_versions}}"">
<div class="container" data-aos="fade-up">
<div class="row justify-content-left">
<form class="col-12" onsubmit="handleSubmission(event)">
Expand All @@ -8,30 +10,39 @@
{% if versions %}
<div class="col-sm-4 col-md-3 form-group">
<p>{{control}} Version</p>
<select name="control-version" id="control_version" class="form-control">
<div class="custom-select" id="control_version">
<select name="control-version" id="control_version_select">
<option value="0">Select version:</option>
{% for v in versions %}
<option>{{v}}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
<div class="col-sm-4 col-md-3 form-group">
<p>ATT&CK Version</p>
<select name="attack-version" id="attack_version" class="form-control">
<div class="custom-select" id="attack_version">
<select name="attack-version" id="attack_version_select">
<option value="0">Select version:</option>
{% for v in attackVersions %}
<option>{{v}}</option>
<option>{{v}}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-sm-4 col-md-3 form-group">
<p>ATT&CK Domain</p>
<select name="domain" id="attack_domain" class="form-control">
<div class="custom-select" id="attack_domain">
<select name="domain" id="attack_domain_select">
<option value="0">Select version:</option>
{% for d in domains %}
<option>{{ d|capitalize }}</option>
<option>{{ d|capitalize }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-sm-4 col-md-3 form-group" style="margin-top: 36px;">
<div class="col-sm-4 col-md-3 form-group" style="margin-top: 28px;">
<button class="btn-pill" style="border: none;">See Mappings</button>
</div>
</div>
Expand All @@ -40,34 +51,263 @@
</div>
</section>
<script>
/* parse the valid version combinations found in the meta tag */
v = document.getElementById("version-combos")
z = v.getAttribute('data-versions')
x = z.replaceAll(`'`, ``).replaceAll(` `, ``)
valid_versions = x.split("),(")
for(var i=0; i < valid_versions.length; i++) {
valid_versions[i] = valid_versions[i].replaceAll("(", "").replaceAll(")", "").replaceAll("[", "").replaceAll("]", "").split(",")
}
var x, i, j, l, ll, selElmnt, a, b, c;
/*look for any elements with the class "custom-select":*/
x = document.getElementsByClassName("custom-select");
l = x.length;
for (i = 0; i < l; i++) {
selElmnt = x[i].getElementsByTagName("select")[0];
ll = selElmnt.length;
/*for each element, create a new DIV that will act as the selected item:*/
a = document.createElement("DIV");
a.setAttribute("class", "select-selected");
a.innerHTML = selElmnt.options[selElmnt.selectedIndex].innerHTML;
x[i].appendChild(a);
/*for each element, create a new DIV that will contain the option list:*/
b = document.createElement("DIV");
b.setAttribute("class", "select-items select-hide");
for (j = 1; j < ll; j++) {
/*for each option in the original select element,
create a new DIV that will act as an option item:*/
c = document.createElement("DIV");
c.innerHTML = selElmnt.options[j].innerHTML;
c.addEventListener("click", function(e) {
console.log("click on ", e.srcElement)
// add our own handling of option states to the boilerplate click handling
let control_version = document.getElementById("control_version_select").value;
let attack_version = document.getElementById("attack_version_select").value;
if (e.srcElement.parentNode.parentNode.id == "control_version") {
control_version = e.srcElement.innerHTML
} else if (e.srcElement.parentNode.parentNode.id == "attack_version") {
attack_version = e.srcElement.innerHTML
}
// see if the current selection combination is valid, and if not click on the first available valid combination
if (e.srcElement.parentNode.parentNode.id != "attack_domain") {
attackOptions = [];
versionOptions = [];
if (e.srcElement.parentNode.parentNode.id == "attack_version") {
versionOptions = parseValidOptions(e.srcElement.innerHTML, "control_version", valid_versions)
setVersionStates(versionOptions, attack_version, control_version)
} else {
attackOptions = parseValidOptions(e.srcElement.innerHTML, "attack_version", valid_versions)
setAttackStates(attackOptions, attack_version, control_version)
}
}
/*when an item is clicked, update the original select box,
and the selected item:*/
var y, i, k, s, h, sl, yl;
s = this.parentNode.parentNode.getElementsByTagName("select")[0];
sl = s.length;
h = this.parentNode.previousSibling;
for (i = 0; i < sl; i++) {
if (s.options[i].innerHTML == this.innerHTML) {
s.selectedIndex = i;
h.innerHTML = this.innerHTML;
y = this.parentNode.getElementsByClassName("same-as-selected");
yl = y.length;
for (k = 0; k < yl; k++) {
y[k].removeAttribute("class");
}
this.setAttribute("class", "same-as-selected");
break;
}
}
h.click();
});
b.appendChild(c);
}
x[i].appendChild(b);
a.addEventListener("click", function(e) {
/*when the select box is clicked, close any other select boxes,
and open/close the current select box:*/
e.stopPropagation();
closeAllSelect(this);
this.nextSibling.classList.toggle("select-hide");
this.classList.toggle("select-arrow-active");
});
}
function closeAllSelect(elmnt) {
/*a function that will close all select boxes in the document,
except the current select box:*/
var x, y, i, xl, yl, arrNo = [];
x = document.getElementsByClassName("select-items");
y = document.getElementsByClassName("select-selected");
xl = x.length;
yl = y.length;
for (i = 0; i < yl; i++) {
if (elmnt == y[i]) {
arrNo.push(i)
} else {
y[i].classList.remove("select-arrow-active");
}
}
for (i = 0; i < xl; i++) {
if (arrNo.indexOf(i)) {
x[i].classList.add("select-hide");
}
}
}
/*if the user clicks anywhere outside the select box,
then close all select boxes:*/
document.addEventListener("click", closeAllSelect);
/* set the states of the options in the attack version dropdown
and click one if not a valid version combo */
function setAttackStates(validVersions, attack_version, control_version) {
setValue = !isValidCombo(attack_version, control_version)
d = document.getElementById("attack_version");
options = d.getElementsByClassName("select-items")[0].children;
for (i = 0; i < options.length; i++) {
let match = false;
let clicked = false;
for (k = 0; k < validVersions.length; k++) {
if (validVersions[k] == options[i].innerHTML ) {
match = true;
break;
}
}
if (match) {
options[i].setAttribute("class", "valid-option");
} else {
options[i].setAttribute("class", "invalid-option");
}
}
if (setValue) {
options = d.getElementsByClassName("valid-option")[0].click();
}
}
/* set the states of the options in the control version dropdown
and click one if not a valid version combo */
function setVersionStates(validVersions, attack_version, control_version) {
setValue = !isValidCombo(attack_version, control_version)
d = document.getElementById("control_version");
options = d.getElementsByClassName("select-items")[0].children;
for (i = 0; i < options.length; i++) {
let match = false;
let clicked = false;
for (k = 0; k < validVersions.length; k++) {
if (validVersions[k] == options[i].innerHTML ) {
match = true;
break;
}
}
if (match) {
options[i].setAttribute("class", "valid-option");
} else {
options[i].setAttribute("class", "invalid-option");
}
}
if (setValue) {
options = d.getElementsByClassName("valid-option")[0].click();
}
}
function isValidCombo(attack_version, control_version){
for (i = 0; i < valid_versions.length; i++) {
if (attack_version == valid_versions[i][1] && control_version == valid_versions[i][0]) {
return true;
}
}
return false;
}
/* find what versions for attack or control are valid given a click on an element */
function parseValidOptions(originalValue, returnValue, valid_versions) {
attackOptions = [];
versionOptions = [];
for(var i=0; i < valid_versions.length; i++) {
let row = valid_versions[i];
for(var ii = 0; ii<row.length; ii++) {
let element = row[ii]
if (element == originalValue) {
versionOptions.push(row[0])
attackOptions.push(row[1])
}
}
}
if (returnValue == "attack_version") { return attackOptions }
else { return versionOptions }
}
/* click on an item in the attack version dropdown */
function setAttackValue(value){
d = document.getElementById("attack_version");
options = d.getElementsByClassName("select-items")[0].children;
if (!value) {
options[0].click();
}
for (i = 0; i < options.length; i++) {
if (options[i].innerHTML.toLowerCase() == value.toLowerCase()) {
options[i].click();
return;
}
}
}
/* click on an item in the control version dropdown */
function setValue(id, value){
d = document.getElementById(id);
options = d.getElementsByClassName("select-items")[0].children;
if (!value) {
options[0].click();
}
for (i = 0; i < options.length; i++) {
if (options[i].innerHTML.toLowerCase() == value.toLowerCase()) {
options[i].click();
return;
}
}
}
/* on load, check if there are any versions specified in the url and set them accordingly
if no versions specified, click on first available attack value
and corresponding control will automatically get set */
function parseParams() {
let attackVersion = '';
let controlVersion = '';
let uri = window.location.pathname;
let params = uri.split("/").filter(n => n);
setValue("attack_domain", "Enterprise")
controlVersion = params[params.length -1].split("-")[1];
if (controlVersion) {
document.getElementById("control_version").value = controlVersion;
setValue("control_version", controlVersion)
}
attackVersion = params[params.length -2].split("-")[1];
if (attackVersion) {
document.getElementById("attack_version").value = attackVersion;
}
function handleSubmission(event) {
event.preventDefault()
let newControl = document.getElementById("control_version").value
let newAttack = document.getElementById("attack_version").value
if (params.length > 3) {
params[params.length-1] = params[params.length-3] + "-" + newControl
params[params.length-2] = "attack-" + newAttack
}
else {
params.push("attack-" + newAttack)
params.push(params[params.length-2] + "-" + newControl)
}
let newUrl = params.join("/")
window.location = "/" + newUrl + "/"
setAttackValue(attackVersion)
}
if(!attackVersion && !controlVersion) {
setAttackValue("")
}
}
/* handle click on see mappings button- route to the new version url */
function handleSubmission(event) {
event.preventDefault()
let uri = window.location.pathname;
let params = uri.split("/").filter(n => n);
let newControl = document.getElementById("control_version_select").value
let newAttack = document.getElementById("attack_version_select").value
if (params.length > 3) {
params[params.length-1] = params[params.length-3] + "-" + newControl
params[params.length-2] = "attack-" + newAttack
}
else {
params.push("attack-" + newAttack)
params.push(params[params.length-2] + "-" + newControl)
}
let newUrl = params.join("/")
window.location = "/" + newUrl + "/"
}
window.onload = parseParams
</script>
{% endblock set_version %}
Loading

0 comments on commit e9c35fb

Please sign in to comment.