diff --git a/CHANGELOG.md b/CHANGELOG.md index 14c88fb02c1..44e62547cdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ +# v4.0.5 (2023-09-01) + +## Features + +* The sidebar is now collapsable and displayed properly in mobile view [#450](https://github.com/mitre-attack/attack-website/issues/450) + +## Bugfixes + +* Changed the UUID generation logic to use CONTENT_VERSION and WEBSITE_VERSION as seeds for idempotent UUID creation. This prevents the creation of redundant IndexedDB tables. [#455](https://github.com/mitre-attack/attack-website/issues/455) + # v4.0.4 (2023-08-11) ## Features diff --git a/attack-theme/static/images/ATTACKCon-4.png b/attack-theme/static/images/ATTACKCon-4.png new file mode 100644 index 00000000000..bb0e96c140d Binary files /dev/null and b/attack-theme/static/images/ATTACKCon-4.png differ diff --git a/attack-theme/static/images/attack_matrix_2023_april_preview.png b/attack-theme/static/images/attack_matrix_2023_april_preview.png new file mode 100644 index 00000000000..ad4a6870d05 Binary files /dev/null and b/attack-theme/static/images/attack_matrix_2023_april_preview.png differ diff --git a/attack-theme/static/images/attack_roadmap_2022_october_preview.png b/attack-theme/static/images/attack_roadmap_2022_october_preview.png new file mode 100644 index 00000000000..a3ed72e1b58 Binary files /dev/null and b/attack-theme/static/images/attack_roadmap_2022_october_preview.png differ diff --git a/attack-theme/static/scripts/navigation.js b/attack-theme/static/scripts/navigation.js index 606fed0da1c..ef3d7f937d7 100644 --- a/attack-theme/static/scripts/navigation.js +++ b/attack-theme/static/scripts/navigation.js @@ -8,6 +8,10 @@ $(document).ready(function () { // jumping from one domain to another. E.g. techniques to matrices let current_modules = window.location.pathname.split("/"); + var sidebar = document.querySelector(".sidebar"); + var sidebarSize = localStorage.getItem('sidebarWidth'); + sidebar.style.width = sidebarSize; + if (document.referrer) { // Loop through the modules in case page is hosted from different // directory diff --git a/attack-theme/static/scripts/resizer.js b/attack-theme/static/scripts/resizer.js index 7dbe2b144c0..eb6b3972bf1 100644 --- a/attack-theme/static/scripts/resizer.js +++ b/attack-theme/static/scripts/resizer.js @@ -1,9 +1,10 @@ +//This code is for being able to resize the sidebar in the desktop view var resizer = document.querySelector(".resizer"); var sidebar = document.querySelector(".sidebar"); -$(document).ready(function (){ -resizeSidebar( resizer, sidebar ); -}); +if(resizer!=null) { + resizeSidebar( resizer, sidebar ); +} function resizeSidebar( resizer, sidebar ) { var x = 0; @@ -21,6 +22,7 @@ function resizeSidebar_mousemoveHandler( event ) { var dx = event.clientX - x; var newsidebarWidth = w + dx; sidebar.style.width = `${ newsidebarWidth }px`; + localStorage.setItem("sidebarWidth", sidebar.style.width); } function resizeSidebar_mouseupHandler() { @@ -29,4 +31,21 @@ function resizeSidebar_mouseupHandler() { } resizer.addEventListener("mousedown", resizeSidebar_mousedownHandler); -} \ No newline at end of file +} + +//This code is for creating a collapsable sidebar for the mobile view +const mediaQuery = window.matchMedia('(max-width: 47.9875rem)') + +function mobileSidenav(e) { + if (e.matches) { + $('#sidebar-collapse').collapse('hide') + } + else{ + $('#sidebar-collapse').collapse('show') + } +} +$(document).ready(function() { + mobileSidenav(mediaQuery) +}); + +mediaQuery.addEventListener('change', mobileSidenav) \ No newline at end of file diff --git a/attack-theme/static/style/_layouts.scss b/attack-theme/static/style/_layouts.scss index a9094fb033d..aeabc3ea157 100644 --- a/attack-theme/static/style/_layouts.scss +++ b/attack-theme/static/style/_layouts.scss @@ -125,6 +125,18 @@ a { border-top: 1px solid border-color(body); } } +.row-main-page { + display: flex; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; +} + +@media screen and (max-width: 47.9875rem) { + .row-main-page { + display: inline; + } +} // p for home page .p-line { @@ -578,17 +590,21 @@ pre { /*TWITTER*/ // twitter container in the home page -.twitter-card { - width: to-rem(400); +.attack-box { + width: to-rem(384); max-width: 100%; - height: to-rem(400); + height: to-rem(464); margin: 0 auto; + border: 3px solid #dfdfdf; + padding: 3px; + display: flex; + flex-direction: column; iframe { border: 1px solid border-color(body) !important; border-radius: .25rem; } - } + /******/ /*BREADCRUMBS*/ diff --git a/attack-theme/static/style/_nav.scss b/attack-theme/static/style/_nav.scss index 81f2ecdd227..ffd4e0b80e9 100644 --- a/attack-theme/static/style/_nav.scss +++ b/attack-theme/static/style/_nav.scss @@ -119,8 +119,39 @@ font-size: rem(1.3); color: on-color-deemphasis(body); letter-spacing: to-rem(3); + pointer-events: none; + @media screen and (max-width: 47.9875rem) { + pointer-events: all; + } + } + + .heading.collapsed .fa-chevron-up, + .heading .fa-chevron-down { + display: none; + } + + .heading.collapsed .fa-chevron-down, + .heading .fa-chevron-up { + display: inline-block; + } + + i.fa { + visibility: hidden; + display: none; + @media screen and (max-width: 47.9875rem) { + visibility: visible; + display: contents; + } } + .br-mobile { + display: none; + @media screen and (max-width: 47.9875rem) { + display: inline-block; + } + } + + // dropdown of the button in the side navigation. This button is in MATRICES, TACTICS, TECHNIQUES, MITIGATIONS, GROUPS, and SOFTWARE .heading-dropdown { font-size: rem(0.9); @@ -176,33 +207,9 @@ } } } - - /* Side navigation collapse */ - // don't show collapsed side navigation (desktop view) - .side-nav-mobile-view { - display: none; - } - - // show side navigation in desktop view - .side-nav-desktop-view { - display: block; - } - - // side navigation collapsed view for phones and small tablets - @media screen and (max-width: 47.9875rem) { - // show collapsed side navigation (mobile view) - .side-nav-mobile-view { - display: block; - } - - // don't show side navigation in desktop view - .side-nav-desktop-view { - display: none; - } - } - /******/ } /******/ + .resizer { width: 2px; top: 0; @@ -212,6 +219,7 @@ position: absolute; background-color: #dfdfdf; } + .sidebar.nav { // max-height: 60vh; overflow-y: auto; @@ -222,13 +230,7 @@ // Remove overflow and sticky position for mobile view @media screen and (max-width: 47.9875rem) { position: static; - } - .side-nav-mobile-view { - .sidenav-wrapper { - .sidenav-list { - overflow-y: visible; - } - } + min-width: fit-content; } .sidenav-wrapper { @@ -238,6 +240,7 @@ height: 100%; display: flex; flex-direction: column; + padding-right: 5px; .heading { border-bottom: 1px solid color-alternate(body); flex: 0 1 0; diff --git a/attack-theme/templates/general/attack-index.html b/attack-theme/templates/general/attack-index.html index abed27a617d..e909f8244bb 100644 --- a/attack-theme/templates/general/attack-index.html +++ b/attack-theme/templates/general/attack-index.html @@ -8,13 +8,9 @@ {{ super() }}
-
+
{% if parsed.attack_branding %}
-

MITRE ATT&CK® is a globally-accessible knowledge base of adversary tactics and techniques based on real-world observations. The ATT&CK knowledge base is used as a foundation for the development of specific threat models and methodologies in the private sector, in government, and in the cybersecurity product and service community.

-

With the creation of ATT&CK, MITRE is fulfilling its mission to solve problems for a safer world — by bringing communities together to develop more effective cybersecurity. ATT&CK is open and available to any person or organization for use at no charge.

-
-
@@ -63,6 +59,21 @@
--> +

MITRE ATT&CK® is a globally-accessible knowledge base of adversary tactics and techniques based on real-world observations. The ATT&CK knowledge base is used as a foundation for the development of specific threat models and methodologies in the private sector, in government, and in the cybersecurity product and service community.

+

With the creation of ATT&CK, MITRE is fulfilling its mission to solve problems for a safer world — by bringing communities together to develop more effective cybersecurity. ATT&CK is open and available to any person or organization for use at no charge.

+
+
+
+ ATT&CKcon 4.0 +
+
+

+ ATT&CKcon 4.0 will be held on Oct 24-25 in McLean, VA. +
+ Click here for more details and to register. +

+
+
{% else %}

diff --git a/attack-theme/templates/macros/navigation.html b/attack-theme/templates/macros/navigation.html index bf1357a395b..4a385b5f279 100644 --- a/attack-theme/templates/macros/navigation.html +++ b/attack-theme/templates/macros/navigation.html @@ -20,7 +20,10 @@ --> {% macro sidenav(root, output_file, filter=False) %}

-
{{root.name | upper}}
+
{{root.name | upper}} + + +
{% if filter %}
@@ -37,6 +40,8 @@
{% endif %} +
+ +
{% endmacro %} diff --git a/data/faq.json b/data/faq.json index fa7aca72cb5..63ff2a2e266 100644 --- a/data/faq.json +++ b/data/faq.json @@ -102,7 +102,7 @@ "questions": [ { "question": "How should I reference the name ATT&CK?", - "answer": "

Both MITRE ATT&CK® and ATT&CK® are registered trademarks of The MITRE Corporation.

For more information please visit our Brand Guide

" + "answer": "

Both MITRE ATT&CK® and ATT&CK® are registered trademarks of The MITRE Corporation.

  • Your first references in writing must include \"MITRE\" preceding \"ATT&CK®\" - but subsequently should just reference \"ATT&CK\" (no registered trademark symbol required).
    • Example of a first reference: MITRE ATT&CK® is a curated knowledge base and model for cyber adversary behavior...
    • Example of subsequent reference: ATT&CK is useful for understanding security risk against known adversary behavior...
  • A headline should always reference \"MITRE ATT&CK\" together (never only \"ATT&CK®\").
  • Always capitalize \"ATT&CK\" to distinguish it from the surrounding text.
  • Do not modify the trademark, such as through hyphenation or abbreviation. For example, \"ATT&CK'd!\", \"Plan-of-ATT&CK\", \"ATTK\".
  • You may not display the ATT&CK trademark in any manner that implies an affiliation with, sponsorship, or endorsement by MITRE, or in a manner that can be reasonably interpreted to suggest third party content represents the views and opinions of MITRE or MITRE personnel, unless those third parties receive express permission from MITRE.
  • You may not use ATT&CK in your product names, service names, trademarks, logos, or company names.
For more information please visit our Brand Guide " }, { "question": "Where can I download the MITRE ATT&CK logo?", diff --git a/data/resources.json b/data/resources.json index 4553412fe70..2b2f9ee8ab3 100644 --- a/data/resources.json +++ b/data/resources.json @@ -3,7 +3,7 @@ { "name": "Automation: The Wonderful Wizard of CTI (Or Is IT?)", "date": "January 2020", - "url": "https://www.sans.org/presentations/automation-the-wonderful-wizard-of-cti-or-is-it/", + "url": "https://www.slideshare.net/MITREATTACK/automation-the-wonderful-wizard-of-cti-or-is-it", "description": "This presentation from the SANS CTI Summit explores how automation can be applied to cyber threat intelligence using the Threat Report ATT&CK Mapper (TRAM)." }, { @@ -22,7 +22,7 @@ "name": "Turning Intelligence Into Action with MITRE ATT&CK", "date": "October 2019", "url": "https://www.anomali.com/resources/webcasts/turning-intelligence-into-action-with-mitre-attck-detect-19-presentation-series", - "description": "This presentation from Anomali Detect discusses how you can use ATT&CK for threat intelligence, including a process for mapping intelligence to ATT&CK as well as biases to watch out for as you do this. Slides are also available." + "description": "This presentation from Anomali Detect discusses how you can use ATT&CK for threat intelligence, including a process for mapping intelligence to ATT&CK as well as biases to watch out for as you do this. Slides are also available." }, { "name": "Leveraging MITRE ATT&CK for Detection, Analysis & Defense", @@ -57,7 +57,7 @@ { "name": "Do-It-Yourself ATT&CK Evaluations to Improve Your Security Posture", "date": "June 2019", - "url": "https://www.sans.org/cyber-security-summit/archives/file/summit_archive_1559672321.pdf", + "url": " https://www.sans.org/presentations/do-it-yourself-att-ck-evaluations-to-improve-your-security-posture/", "description": "This presentation from the SANS Enterprise Defense Summit explains how defenders can improve their security posture through the use of adversary emulation by performing their very own ATT&CK Evaluations." }, { @@ -123,7 +123,7 @@ { "name": "ATT&CKing the Status Quo: Threat-Based Adversary Emulation with MITRE ATT&CK", "date": "September 2018", - "url": "https://www.sans.org/cyber-security-summit/archives/file/summit-archive-1536260992.pdf", + "url": "https://www.slideshare.net/KatieNickels/threatbased-adversary-emulation-with-mitre-attck", "description": "This presentation from the SANS Threat Hunting Summit shows how you can use ATT&CK to apply threat intelligence to adversary emulation." }, { diff --git a/modules/campaigns/campaigns.py b/modules/campaigns/campaigns.py index 7e012787fc1..0a4eba57575 100644 --- a/modules/campaigns/campaigns.py +++ b/modules/campaigns/campaigns.py @@ -63,12 +63,6 @@ def generate_markdown_files(): "Campaigns", "/campaigns/", campaign_list_no_deprecated_revoked ) data["side_menu_data"] = side_menu_data - - side_menu_mobile_view_data = util.buildhelpers.get_side_menu_mobile_view_data( - "campaigns", "/campaigns/", campaign_list_no_deprecated_revoked, group_by - ) - data["side_menu_mobile_view_data"] = side_menu_mobile_view_data - data["campaigns_table"] = get_campaigns_table_data(campaign_list_no_deprecated_revoked) data["campaigns_list_len"] = str(len(campaign_list_no_deprecated_revoked)) @@ -81,12 +75,12 @@ def generate_markdown_files(): # Create the markdown for the enterprise campaigns in the STIX for campaign in campaign_list: - generate_campaign_md(campaign, side_menu_data, side_menu_mobile_view_data, notes) + generate_campaign_md(campaign, side_menu_data, notes) return has_campaign -def generate_campaign_md(campaign, side_menu_data, side_menu_mobile_view_data, notes): +def generate_campaign_md(campaign, side_menu_data, notes): """Responsible for generating markdown of all campaigns.""" attack_id = util.buildhelpers.get_attack_id(campaign) @@ -97,7 +91,6 @@ def generate_campaign_md(campaign, side_menu_data, side_menu_mobile_view_data, n data["attack_id"] = attack_id data["side_menu_data"] = side_menu_data - data["side_menu_mobile_view_data"] = side_menu_mobile_view_data data["notes"] = notes.get(campaign["id"]) # External references diff --git a/modules/campaigns/templates/campaign.html b/modules/campaigns/templates/campaign.html index c733bf73b40..24ddfbc0fe5 100644 --- a/modules/campaigns/templates/campaign.html +++ b/modules/campaigns/templates/campaign.html @@ -27,12 +27,9 @@ {% block innerleft %} -
+
{{ navigation.sidenav(parsed.side_menu_data, output_file) }}
-
- {{ navigation.sidenav(parsed.side_menu_mobile_view_data, output_file) }} -
{% endblock %} diff --git a/modules/campaigns/templates/campaigns-index.html b/modules/campaigns/templates/campaigns-index.html index 0c09ef97530..7787ed35046 100644 --- a/modules/campaigns/templates/campaigns-index.html +++ b/modules/campaigns/templates/campaigns-index.html @@ -7,12 +7,9 @@ {% block innerleft %} -
+
{{ navigation.sidenav(parsed.side_menu_data, output_file) }}
-
- {{ navigation.sidenav(parsed.side_menu_mobile_view_data, output_file) }} -
{% endblock %} diff --git a/modules/datasources/datasources.py b/modules/datasources/datasources.py index a5b51681a23..17a9dfe84f4 100644 --- a/modules/datasources/datasources.py +++ b/modules/datasources/datasources.py @@ -52,12 +52,6 @@ def generate_markdown_files(): notes = rsg.get_objects_using_notes() side_menu_data = get_datasources_side_nav_data(datasource_list_no_deprecated_revoked) data["side_menu_data"] = side_menu_data - - side_menu_mobile_view_data = util.buildhelpers.get_side_menu_mobile_view_data( - datasources_config.module_name, "/datasources/", datasource_list_no_deprecated_revoked, group_by - ) - data["side_menu_mobile_view_data"] = side_menu_mobile_view_data - data["datasources_table"] = get_datasources_table_data(datasource_list_no_deprecated_revoked) data["datasources_list_len"] = str(len(datasource_list_no_deprecated_revoked)) @@ -70,12 +64,12 @@ def generate_markdown_files(): # Create the markdown for the enterprise datasources in the STIX for datasource in datasource_list: - generate_datasource_md(datasource, side_menu_data, side_menu_mobile_view_data, notes) + generate_datasource_md(datasource, side_menu_data, notes) return has_datasource -def generate_datasource_md(datasource, side_menu_data, side_menu_mobile_view_data, notes): +def generate_datasource_md(datasource, side_menu_data, notes): """Responsible for generating markdown of all datasources.""" attack_id = util.buildhelpers.get_attack_id(datasource) @@ -85,7 +79,6 @@ def generate_datasource_md(datasource, side_menu_data, side_menu_mobile_view_dat data["attack_id"] = attack_id data["side_menu_data"] = side_menu_data - data["side_menu_mobile_view_data"] = side_menu_mobile_view_data data["notes"] = notes.get(datasource["id"]) # Get initial reference list @@ -231,12 +224,21 @@ def get_datasources_table_data(datasource_list): # Now the table on the right, which is made up of datasource data for datasource in datasource_list: attack_id = util.buildhelpers.get_attack_id(datasource) + domain_list = util.buildhelpers.get_domain_name(datasource) if attack_id: row = {} row["id"] = attack_id + for domain_idx in range(len(domain_list)): + domain_list[domain_idx] = domain_list[domain_idx].replace('-attack','') + if domain_list[domain_idx] == "ics": + domain_list[domain_idx] = domain_list[domain_idx].upper() + else: + domain_list[domain_idx] = domain_list[domain_idx].capitalize() + row["domains"] = domain_list + if datasource.get("name"): row["name"] = datasource["name"] diff --git a/modules/datasources/templates/datasource.html b/modules/datasources/templates/datasource.html index 148f36ef883..8e7ce56c470 100644 --- a/modules/datasources/templates/datasource.html +++ b/modules/datasources/templates/datasource.html @@ -27,12 +27,9 @@ {% block innerleft %} -
+
{{ navigation.sidenav(parsed.side_menu_data, output_file, true) }}
-
- {{ navigation.sidenav(parsed.side_menu_mobile_view_data, output_file) }} -
{% endblock %} diff --git a/modules/datasources/templates/datasources-index.html b/modules/datasources/templates/datasources-index.html index d54ea96d07c..9a9348972d4 100644 --- a/modules/datasources/templates/datasources-index.html +++ b/modules/datasources/templates/datasources-index.html @@ -7,12 +7,9 @@ {% block innerleft %} -
+
{{ navigation.sidenav(parsed.side_menu_data, output_file, true) }}
-
- {{ navigation.sidenav(parsed.side_menu_mobile_view_data, output_file) }} -
{% endblock %} @@ -36,6 +33,7 @@
Data Sources: {{ parsed.datasources_list_len }} ID Name + Domain Description @@ -48,6 +46,12 @@
Data Sources: {{ parsed.datasources_list_len }} {{row.name}} + + {% for dom in row.domains %} + {{dom}} +
+ {% endfor %} + {% if row.deprecated %} ****Deprecation Warning**** diff --git a/modules/groups/groups.py b/modules/groups/groups.py index 0de9fdf4f93..5b2c888bd6c 100644 --- a/modules/groups/groups.py +++ b/modules/groups/groups.py @@ -65,11 +65,6 @@ def generate_markdown_files(): side_menu_data = util.buildhelpers.get_side_menu_data("Groups", "/groups/", group_list_no_deprecated_revoked) data["side_menu_data"] = side_menu_data - side_menu_mobile_view_data = util.buildhelpers.get_side_menu_mobile_view_data( - "groups", "/groups/", group_list_no_deprecated_revoked, group_by - ) - data["side_menu_mobile_view_data"] = side_menu_mobile_view_data - data["groups_table"] = get_groups_table_data(group_list_no_deprecated_revoked) data["groups_list_len"] = str(len(group_list_no_deprecated_revoked)) @@ -80,12 +75,12 @@ def generate_markdown_files(): # Create the markdown for the enterprise groups in the STIX for group in group_list: - generate_group_md(group, side_menu_data, side_menu_mobile_view_data, notes) + generate_group_md(group, side_menu_data, notes) return has_group -def generate_group_md(group, side_menu_data, side_menu_mobile_view_data, notes): +def generate_group_md(group, side_menu_data, notes): """Responsible for generating markdown of all groups""" attack_id = util.buildhelpers.get_attack_id(group) @@ -94,9 +89,7 @@ def generate_group_md(group, side_menu_data, side_menu_mobile_view_data, notes): data = {} data["attack_id"] = attack_id - data["side_menu_data"] = side_menu_data - data["side_menu_mobile_view_data"] = side_menu_mobile_view_data data["notes"] = notes.get(group["id"]) # External references diff --git a/modules/groups/templates/group.html b/modules/groups/templates/group.html index f072503aa69..c2247770c39 100644 --- a/modules/groups/templates/group.html +++ b/modules/groups/templates/group.html @@ -27,12 +27,9 @@ {% block innerleft %} -
+
{{ navigation.sidenav(parsed.side_menu_data, output_file) }}
-
- {{ navigation.sidenav(parsed.side_menu_mobile_view_data, output_file) }} -
{% endblock %} diff --git a/modules/groups/templates/groups-index.html b/modules/groups/templates/groups-index.html index abea03bc3d6..382d907dd7a 100644 --- a/modules/groups/templates/groups-index.html +++ b/modules/groups/templates/groups-index.html @@ -7,12 +7,9 @@ {% block innerleft %} -
+
{{ navigation.sidenav(parsed.side_menu_data, output_file) }}
-
- {{ navigation.sidenav(parsed.side_menu_mobile_view_data, output_file) }} -
{% endblock %} diff --git a/modules/mitigations/mitigations.py b/modules/mitigations/mitigations.py index 06a8b91b130..e6e6eb64e3c 100644 --- a/modules/mitigations/mitigations.py +++ b/modules/mitigations/mitigations.py @@ -45,13 +45,11 @@ def generate_mitigations(): notes = util.relationshipgetters.get_objects_using_notes() side_nav_data = util.buildhelpers.get_side_nav_domains_data("mitigations", mitigations) - side_nav_mobile_data = util.buildhelpers.get_side_nav_domains_mobile_view_data("mitigations", mitigations, group_by) - for domain in site_config.domains: if domain["deprecated"]: continue check_if_generated = generate_markdown_files( - domain["name"], mitigations_with_deprecated[domain["name"]], side_nav_data, side_nav_mobile_data, notes + domain["name"], mitigations_with_deprecated[domain["name"]], side_nav_data, notes ) if not mitigation_generated: if check_if_generated: @@ -61,7 +59,7 @@ def generate_mitigations(): util.buildhelpers.remove_module_from_menu(mitigations_config.module_name) -def generate_markdown_files(domain, mitigations, side_nav_data, side_nav_mobile_data, notes): +def generate_markdown_files(domain, mitigations, side_nav_data, notes): """Responsible for generating shared data between all mitigation pages and begins mitigation markdown generation.""" data = {} @@ -72,7 +70,6 @@ def generate_markdown_files(domain, mitigations, side_nav_data, side_nav_mobile_ ] data["mitigation_list_len"] = str(len(non_deprecated_mitigations)) data["side_menu_data"] = side_nav_data - data["side_menu_mobile_view_data"] = side_nav_mobile_data data["mitigation_table"] = get_mitigation_table_data(non_deprecated_mitigations) @@ -85,7 +82,7 @@ def generate_markdown_files(domain, mitigations, side_nav_data, side_nav_mobile_ # Generates the markdown files to be used for page generation for mitigation in mitigations: - generate_mitigation_md(mitigation, domain, side_nav_data, side_nav_mobile_data, notes) + generate_mitigation_md(mitigation, domain, side_nav_data, notes) return True @@ -93,7 +90,7 @@ def generate_markdown_files(domain, mitigations, side_nav_data, side_nav_mobile_ return False -def generate_mitigation_md(mitigation, domain, side_menu_data, side_menu_mobile_data, notes): +def generate_mitigation_md(mitigation, domain, side_menu_data, notes): """Generates the markdown for the given mitigation""" attack_id = util.buildhelpers.get_attack_id(mitigation) @@ -104,7 +101,6 @@ def generate_mitigation_md(mitigation, domain, side_menu_data, side_menu_mobile_ data["domain"] = domain.split("-")[0] data["side_menu_data"] = side_menu_data - data["side_menu_mobile_view_data"] = side_menu_mobile_data data["name"] = mitigation["name"] data["notes"] = notes.get(mitigation["id"]) diff --git a/modules/mitigations/templates/mitigation.html b/modules/mitigations/templates/mitigation.html index a7edc6649bc..95515399a6a 100644 --- a/modules/mitigations/templates/mitigation.html +++ b/modules/mitigations/templates/mitigation.html @@ -26,12 +26,9 @@ {% block innerleft %} -
+
{{ navigation.sidenav(parsed.side_menu_data, output_file) }}
-
- {{ navigation.sidenav(parsed.side_menu_mobile_view_data, output_file) }} -
{% endblock %} diff --git a/modules/mitigations/templates/mitigations-domain-index.html b/modules/mitigations/templates/mitigations-domain-index.html index ca21e016fae..509ead41ec5 100644 --- a/modules/mitigations/templates/mitigations-domain-index.html +++ b/modules/mitigations/templates/mitigations-domain-index.html @@ -14,12 +14,9 @@ {% block innerleft %} -
+
{{ navigation.sidenav(parsed.side_menu_data, output_file) }}
-
- {{ navigation.sidenav(parsed.side_menu_mobile_view_data, output_file) }} -
{% endblock %} diff --git a/modules/resources/docs/attack_matrix_poster_2023_april.pdf b/modules/resources/docs/attack_matrix_poster_2023_april.pdf new file mode 100644 index 00000000000..e4d3ffcce24 Binary files /dev/null and b/modules/resources/docs/attack_matrix_poster_2023_april.pdf differ diff --git a/modules/resources/docs/attack_roadmap_2022_october.pdf b/modules/resources/docs/attack_roadmap_2022_october.pdf new file mode 100644 index 00000000000..e49680f65a5 Binary files /dev/null and b/modules/resources/docs/attack_roadmap_2022_october.pdf differ diff --git a/modules/resources/templates/resources.html b/modules/resources/templates/resources.html index 17abc1986d3..bab4d488e9b 100644 --- a/modules/resources/templates/resources.html +++ b/modules/resources/templates/resources.html @@ -110,18 +110,18 @@

Graphics

- Card image cap + Card image cap
- MITRE ATT&CK Roadmap -

Last updated October 2020

+ MITRE ATT&CK Roadmap +

Last updated October 2022

- Card image cap + Card image cap
- MITRE ATT&CK Matrix Poster -

Last updated June 2021

+ MITRE ATT&CK Matrix Poster +

Last updated April 2023

diff --git a/modules/software/software.py b/modules/software/software.py index 73a9654b2b4..b93a6faee0b 100644 --- a/modules/software/software.py +++ b/modules/software/software.py @@ -55,12 +55,8 @@ def generate_markdown_files(): side_menu_data = util.buildhelpers.get_side_menu_data( "software", "/software/", software_list_no_deprecated_revoked ) - side_menu_mobile_view_data = util.buildhelpers.get_side_menu_mobile_view_data( - "software", "/software/", software_list_no_deprecated_revoked, group_by - ) - + data["side_menu_data"] = side_menu_data - data["side_menu_mobile_view_data"] = side_menu_mobile_view_data data["software_table"] = get_software_table_data(software_list_no_deprecated_revoked) subs = software_config.software_index_md + json.dumps(data) @@ -69,12 +65,12 @@ def generate_markdown_files(): # Create the markdown for the enterprise groups in the stix for software in software_list: - generate_software_md(software, side_menu_data, side_menu_mobile_view_data, notes) + generate_software_md(software, side_menu_data, notes) return has_software -def generate_software_md(software, side_menu_data, side_menu_mobile_view_data, notes): +def generate_software_md(software, side_menu_data, notes): """Responsible for generating given software markdown""" attack_id = util.buildhelpers.get_attack_id(software) @@ -85,7 +81,6 @@ def generate_software_md(software, side_menu_data, side_menu_mobile_view_data, n data["attack_id"] = attack_id data["side_menu_data"] = side_menu_data - data["side_menu_mobile_view_data"] = side_menu_mobile_view_data data["notes"] = notes.get(software["id"]) dates = util.buildhelpers.get_created_and_modified_dates(software) diff --git a/modules/software/templates/software-index.html b/modules/software/templates/software-index.html index 9b7ffe7ee4b..573481e8c33 100644 --- a/modules/software/templates/software-index.html +++ b/modules/software/templates/software-index.html @@ -7,12 +7,9 @@ {% block innerleft %} -
+
{{ navigation.sidenav(parsed.side_menu_data, output_file) }}
-
- {{ navigation.sidenav(parsed.side_menu_mobile_view_data, output_file) }} -
{% endblock %} diff --git a/modules/software/templates/software.html b/modules/software/templates/software.html index 324661ce109..0e1275cf6e0 100644 --- a/modules/software/templates/software.html +++ b/modules/software/templates/software.html @@ -21,12 +21,9 @@ {% block innerleft %} -
+
{{ navigation.sidenav(parsed.side_menu_data, output_file) }}
-
- {{ navigation.sidenav(parsed.side_menu_mobile_view_data, output_file) }} -
{% endblock %} diff --git a/modules/tactics/templates/tactic.html b/modules/tactics/templates/tactic.html index 1fcd435aa76..5ae8e3aa715 100644 --- a/modules/tactics/templates/tactic.html +++ b/modules/tactics/templates/tactic.html @@ -25,7 +25,7 @@ {% block innerleft %} -
+
{{ navigation.sidenav(parsed.side_menu_data, output_file) }}
diff --git a/modules/tactics/templates/tactics-domain-index.html b/modules/tactics/templates/tactics-domain-index.html index 2282c5a3b88..ab231368b91 100644 --- a/modules/tactics/templates/tactics-domain-index.html +++ b/modules/tactics/templates/tactics-domain-index.html @@ -23,7 +23,7 @@ {% block innerleft %} -
+
{{ navigation.sidenav(parsed.side_menu_data, output_file) }}
diff --git a/modules/util/buildhelpers.py b/modules/util/buildhelpers.py index 5751d5ecd3d..ea0c6eb92ab 100644 --- a/modules/util/buildhelpers.py +++ b/modules/util/buildhelpers.py @@ -100,6 +100,11 @@ def get_attack_id(object): return None +def get_domain_name(object): + """Given an object, return domains.""" + return object.get("x_mitre_domains") + + def update_reference_list(reference_list, obj): """Given a reference list and an object, update the reference list with the external references found in the object.""" if obj.get("external_references"): @@ -289,101 +294,6 @@ def get_element_data(element): } -def get_side_nav_domains_mobile_view_data(side_nav_title, elements_list, amount_per_row): - """Given a title, an elements list and the amount of elements per row, get the data for the side navigation on a mobile view.""" - - def get_element_data(element): - """Given an element, return the formatted JSON.""" - return { - "name": element["name"], - "id": uuid.uuid4().hex, - "path": "/{}/{}/".format(side_nav_title, attack_id), - "children": [], - } - - def check_children(category_list, domain_list): - """Given a category list, check if there is no children and update list. Ignore if is digits or other.""" - for cat in category_list: - if not cat["children"]: - if cat["name"].startswith("1"): - continue - elif cat["name"].startswith("Other"): - continue - else: - # Add empty child - child = {"name": "No {}".format(side_nav_title), "id": "empty", "path": None, "children": []} - cat["children"].append(child) - domain_data["children"].append(cat) - - return domain_data - - def get_category_list(): - """Get an empty category list.""" - caterogories_content = [] - for cat in categories: - pane = {"name": cat, "id": uuid.uuid4().hex, "path": None, "children": []} - caterogories_content.append(pane) - - return caterogories_content - - categories_map = {char: math.ceil((i + 1) / amount_per_row) for i, char in enumerate(string.ascii_uppercase)} - categories = list( - map( - lambda cat: cat[0] + "-" + cat[-1], - [ - string.ascii_uppercase[i : i + amount_per_row] - for i in range(0, len(string.ascii_uppercase), amount_per_row) - ], - ) - ) - categories = ["1-9"] + categories + ["Other"] - - elements_data = [] - - for domain in site_config.domains: - if domain["deprecated"]: - continue - if elements_list[domain["name"]]: - caterogy_list = get_category_list() - - domain_data = { - "name": domain["alias"], - "id": domain["alias"], - "path": "/{}/{}/".format(side_nav_title, domain["name"].split("-")[0]), - "children": [], - } - - for element in elements_list[domain["name"]]: - attack_id = get_attack_id(element) - if attack_id: - child = get_element_data(element) - - # Get first character and find in map - element_char = element["name"][0] - - if element_char.isdigit(): - element_cat_index = 0 - elif element_char.isalpha(): - element_cat_index = categories_map[element_char.upper()] - else: - element_cat_index = len(categories) - 1 - - # Add child to pane - caterogy_list[element_cat_index]["children"].append(child) - - domain_data = check_children(caterogy_list, domain_data) - - elements_data.append(domain_data) - - # return side menu - return { - "name": side_nav_title, - "id": side_nav_title, - "path": None, # root level doesn't get a path - "children": elements_data, - } - - def get_side_menu_data(side_nav_title, path_prefix, elements_list, domain=None): """Responsible for generating the links that are located on the left side of pages for desktop clients.""" @@ -412,79 +322,6 @@ def get_side_menu_data(side_nav_title, path_prefix, elements_list, domain=None): } -def get_side_menu_mobile_view_data(side_nav_title, path_prefix, elements_list, amount_per_row, domain=None): - """Responsible for generating the links that are located on the left side of pages for mobile devices.""" - categories_map = {char: math.ceil((i + 1) / amount_per_row) for i, char in enumerate(string.ascii_uppercase)} - categories = list( - map( - lambda cat: cat[0] + "-" + cat[-1], - [ - string.ascii_uppercase[i : i + amount_per_row] - for i in range(0, len(string.ascii_uppercase), amount_per_row) - ], - ) - ) - categories = ["1-9"] + categories + ["Other"] - - mobile_side_table_data = [] - - caterogories_content = [] - for cat in categories: - pane = {"name": cat, "id": uuid.uuid4().hex, "path": None, "children": []} - caterogories_content.append(pane) - - for element in elements_list: - # Fill rows for each category - attack_id = get_attack_id(element) - - if attack_id: - child = { - "name": element["name"], - "id": uuid.uuid4().hex, - "path": path_prefix + attack_id + "/", - "children": [], - } - - # Get first character and find in map - element_char = element["name"][0] - - if element_char.isdigit(): - element_cat_index = 0 - elif element_char.isalpha(): - element_cat_index = categories_map[element_char.upper()] - else: - element_cat_index = len(categories) - 1 - - # Add child to pane - caterogories_content[element_cat_index]["children"].append(child) - - # Add categories to mobile side navigation - # Remove "1-9" and "Other" categories if empty - # Add "No [Type]" to empty object, side_nav_title will hold the type of the page - - for cat in caterogories_content: - if not cat["children"]: - if cat["name"].startswith("1"): - continue - elif cat["name"].startswith("Other"): - continue - else: - # Add empty child - child = {"name": "No {}".format(side_nav_title), "id": "empty", "path": None, "children": []} - cat["children"].append(child) - mobile_side_table_data.append(cat) - - if domain: - path_prefix += domain + "/" - # return side menu - return { - "name": side_nav_title, - "id": side_nav_title, - "path": path_prefix, # root level doesn't get a path - "children": mobile_side_table_data, - } - - def is_tid(tid): """Check if input has a tid pattern.""" pattern = re.compile("^T[0-9][0-9][0-9][0-9]$") diff --git a/modules/website_build/website_build.py b/modules/website_build/website_build.py index 4af3d0076f9..1270d672d3c 100644 --- a/modules/website_build/website_build.py +++ b/modules/website_build/website_build.py @@ -2,7 +2,7 @@ import os import shutil import subprocess -import uuid +import hashlib from string import Template from loguru import logger @@ -43,6 +43,25 @@ def generate_website(): remove_unwanted_output() +def generate_uuid_from_seeds(content_version, website_version): + """ + Generate a UUID based on the given content_version and website_version. + + Args: + - content_version (str): Semantic version of the content without a leading 'v'. + - website_version (str): Semantic version of the website with a leading 'v'. + + Returns: + - str: A UUID generated based on the two versions. + """ + # Combine and hash the values + seed = f"{content_version}-{website_version}".encode('utf-8') + hashed_seed = hashlib.md5(seed).hexdigest() + + # Convert the first 32 characters of the hash to a UUID format + return '-'.join([hashed_seed[i:i+length] for i, length in zip([0, 8, 12, 16, 20], [8, 4, 4, 4, 12])]) + + def generate_javascript_settings(): """Creates javascript settings file that will be used to other javascript files""" logger.info("Generating JavaScript settings.js") @@ -70,7 +89,13 @@ def generate_javascript_settings(): web_dir = web_dir + "/" js_data = website_build_config.js_dir_settings.substitute({"web_directory": web_dir}) - build_uuid = str(uuid.uuid4()) + + # Use the content and website versions as a seed for the build UUID to ensure that the UUID is idempotent. + CONTENT_VERSION = website_build_config.base_page_data['CONTENT_VERSION'] + WEBSITE_VERSION = website_build_config.base_page_data['WEBSITE_VERSION'] + + build_uuid = generate_uuid_from_seeds(CONTENT_VERSION, WEBSITE_VERSION) + js_build_uuid = website_build_config.js_build_uuid.substitute({"build_uuid": build_uuid}) js_data += js_build_uuid js_f.write(js_data) diff --git a/pyproject.toml b/pyproject.toml index c478333cfdb..af04b688a95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ profile = "black" [tool.towncrier] name = "ATT&CK website" - version = "4.0.4" + version = "4.0.5" filename = "CHANGELOG.md" issue_format = "[#{issue}](https://github.com/mitre-attack/attack-website/issues/{issue})" template = ".towncrier.template.md" diff --git a/requirements.txt b/requirements.txt index 5112a54e0c4..065c5a9d9b6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -GitPython==3.1.31 +GitPython==3.1.32 Markdown==3.4.3 bleach==6.0.0 colorama==0.4.6