Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/test/unit/web_interface/test_app_jinja_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

@pytest.fixture
def filter_class(web_frontend):
return FilterClass(web_frontend.app, '', web_frontend.db)
return FilterClass(web_frontend.app, web_frontend.db)


class TestAppShowAnalysis:
Expand Down
25 changes: 25 additions & 0 deletions src/test/unit/web_interface/test_app_jinja_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Any

import pytest
from flask import render_template_string

from version import __VERSION__


def test_auth_is_disabled(web_frontend):
_assert_is_rendered(web_frontend, 'auth_is_enabled', False)


@pytest.mark.frontend_config_overwrite({'authentication': {'enabled': True}})
def test_auth_is_enabled(web_frontend):
_assert_is_rendered(web_frontend, 'auth_is_enabled', True)


def test_get_fact_version(web_frontend):
_assert_is_rendered(web_frontend, 'get_fact_version', __VERSION__)


def _assert_is_rendered(web_frontend, function: str, expected_value: Any):
with web_frontend.app.test_request_context():
template = render_template_string(f'<html><body><div>{{{{ {function}() }}}}</div></body></html>')
assert f'<div>{expected_value}</div>' in template
11 changes: 1 addition & 10 deletions src/web_interface/components/jinja_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,12 @@ class FilterClass:
This is WEB front end main class
"""

def __init__(self, app, program_version, db: FrontendDatabase, **_):
self._program_version = program_version
def __init__(self, app, db: FrontendDatabase, **_):
self._app = app
self.db = db

self._setup_filters()

def _filter_print_program_version(self, *_):
return f'{self._program_version}'

def _filter_replace_uid_with_file_name(self, input_data):
tmp = input_data.__str__()
uid_list = flt.get_all_uids_in_string(tmp)
Expand Down Expand Up @@ -117,9 +113,6 @@ def _virtual_path_element_to_span(hid_element: str, uid: str, root_uid: str, cur
'</span>'
)

def check_auth(self, _):
return config.frontend.authentication.enabled

def data_to_chart_limited(self, data, limit: int | None = None, color_list=None):
limit = self._get_chart_element_count() if limit is None else limit
try:
Expand Down Expand Up @@ -150,7 +143,6 @@ def _setup_filters(self):
{
'all_items_equal': lambda data: len({str(value) for value in data.values()}) == 1,
'as_ascii_table': flt.as_ascii_table,
'auth_enabled': self.check_auth,
'base64_encode': flt.encode_base64_filter,
'bytes_to_str': flt.bytes_to_str_filter,
'data_to_chart': self.data_to_chart,
Expand Down Expand Up @@ -189,7 +181,6 @@ def _setup_filters(self):
'nice_virtual_path_list': self._nice_virtual_path_list,
'number_format': flt.byte_number_filter,
'octal_to_readable': flt.octal_to_readable,
'print_program_version': self._filter_print_program_version,
'regex_meta': flt.comment_out_regex_meta_chars,
'remaining_time': elapsed_time,
'render_analysis_tags': flt.render_analysis_tags,
Expand Down
14 changes: 13 additions & 1 deletion src/web_interface/frontend_main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from __future__ import annotations

import logging
from inspect import getmembers, isfunction

from intercom.front_end_binding import InterComFrontEndBinding
from storage.redis_status_interface import RedisStatusInterface
from version import __VERSION__
from web_interface import jinja_functions
from web_interface.app import create_app
from web_interface.components.ajax_routes import AjaxRoutes
from web_interface.components.analysis_routes import AnalysisRoutes
Expand All @@ -29,6 +31,7 @@ def __init__(self, db: FrontendDatabase | None = None, intercom=None, status_int
self.status_interface = RedisStatusInterface() if status_interface is None else status_interface

self._setup_app()
self._register_jinja_functions()
logging.info('Web front end online')

def _setup_app(self):
Expand All @@ -47,4 +50,13 @@ def _setup_app(self):

rest_base = RestBase(**base_args)
PluginRoutes(**base_args, api=rest_base.api)
FilterClass(self.app, self.program_version, self.db)
FilterClass(self.app, self.db)

def _register_jinja_functions(self):
# add functions from the module to the globals in jinja so that the functions can be called from templates
for function_name, function in getmembers(jinja_functions, isfunction):
if (
(not function_name.startswith('_')) # we assume all function beginning with "_" are helper functions
and (function.__module__ == jinja_functions.__name__) # only functions from the module, no imports
):
self.app.jinja_env.globals.update({function_name: function})
10 changes: 10 additions & 0 deletions src/web_interface/jinja_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import config
from version import __VERSION__


def auth_is_enabled() -> bool:
return config.frontend.authentication.enabled


def get_fact_version():
return __VERSION__
162 changes: 65 additions & 97 deletions src/web_interface/templates/base.html
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
<!DOCTYPE html>
{% set navigation_bar = [
("Home", "/", '<i class="fas fa-home"></i>'),
("Database", None, None),
("Upload", "/upload", '<i class="fas fa-upload"></i>'),
("Info", None, '<i class="fas fa-info-circle"></i>'),
("Feedback", None, '<i class="fas fa-bullhorn"></i>'),
("Admin", None, '<i class="fas fa-users-cog"></i>')
] -%}

{% if ("string only here to have input to " | auth_enabled) %}
{% do navigation_bar.append(("Login", None, None)) %}
{% endif %}

{% set active_page = active_page | default('Home') %}

{% macro navbar_item(caption, link, icon, options=None) %}
<li class="pr-3">
<a href="{{ link }}"
{% if caption == active_page %}style="color: #fff; font-weight: 500;"{% endif %}
{% if options %}{{ options | safe }}{% endif %}
><i class="fas fa-{{ icon }}"></i><span class="hideable_caption">&nbsp;{{ caption }}</span></a>
</li>
{%- endmacro %}

{% macro navbar_dropdown_menu(caption, icon) %}
<li class="dropdown pr-3">
<a class="dropdown-toggle"
data-toggle="dropdown"
href="#"
{% if caption == active_page %}style="color: #fff; font-weight: 500;" {% endif %}
><i class="fas fa-{{ icon }}"></i><span class="hideable_caption">&nbsp;{{ caption }}</span><span class="caret"></span></a>
<div class="dropdown-menu">
{{ caller() }}
</div>
</li>
{%- endmacro %}

{% macro navbar_dropdown_item(link, icon, caption) %}
<a class="dropdown-item" href="{{ link }}" style="font-size: 14px">
<i class="fas fa-{{ icon }}"></i> {{ caption }}
</a>
{%- endmacro %}

<html>
<head lang="en">
<meta charset="UTF-8">
Expand Down Expand Up @@ -58,6 +74,7 @@

<body>

{# Navbar #}
<nav role="navigation" class="navbar navbar-expand-lg navbar-light flex-column flex-md-row bg-fact">
<button type="button" data-target="#navbarCollapse" data-toggle="collapse" class="navbar-toggler">
<span class="sr-only">Toggle navigation</span>
Expand All @@ -70,91 +87,42 @@
<div id="navbarCollapse" class="collapse navbar-collapse">
<!-- Navbar Elements -->
<ul class="navbar-nav" style="padding-right: 5px">
{% for caption, href, icon in navigation_bar %}
{% if caption == "Database" %}
<li class="dropdown pr-3">
<a {% if caption== active_page %}style="color: #fff; font-weight: 500;" {% endif %}
class="dropdown-toggle" data-toggle="dropdown" href="#"><i
class="fas fa-tasks"></i><span class="hideable_caption">&nbsp;Database</span><span class="caret"></span></a>
<div class="dropdown-menu">
<a class="dropdown-item" href="/database/browse" style="font-size: 14px"><i
class="fas fa-list-ul"></i> Browse Firmware</a>
<a class="dropdown-item" href="/database/browse_compare" style="font-size: 14px"><i
class="far fa-copy"></i> Browse Compares</a>
<a class="dropdown-item" href="/database/search" style="font-size: 14px"><i
class="fas fa-search"></i> Basic Search</a>
<a class="dropdown-item" href="/database/advanced_search" style="font-size: 14px"><i
class="fas fa-search-plus"></i> Advanced Search</a>
<a class="dropdown-item" href="/database/graphql" style="font-size: 14px"><i
class="fas fa-search-plus"></i> GraphQL Search</a>
<a class="dropdown-item" href="/database/binary_search" style="font-size: 14px"><i
class="fas fa-search-dollar"></i> Binary Pattern Search</a>
<a class="dropdown-item" href="/database/browse_binary_search_history" style="font-size: 14px"><i
class="fas fa-search-dollar"></i> Browse Binary Pattern Search History</a>
</div>
</li>
{% elif caption == "Info" %}
<li class="dropdown pr-3">
<a {% if caption== active_page %}style="color: #fff; font-weight: 500;" {% endif %}
class="dropdown-toggle" data-toggle="dropdown" href="#"><i class="fas fa-info-circle"></i><span class="hideable_caption">&nbsp;{{
caption }}</span><span class="caret"></span></a>
<div class="dropdown-menu">
<a class="dropdown-item" href="/statistic" style="font-size: 14px"><i class="fas fa-chart-bar"></i>
Statistics</a>
<a class="dropdown-item" href="/system_health" style="font-size: 14px"><i
class="fas fa-heartbeat"></i> System</a>
<a class="dropdown-item" href="/plugins" style="font-size: 14px"><i class="fas fa-puzzle-piece"></i> Plugins</a>
<a class="dropdown-item" href="/doc" style="font-size: 14px"><i class="fas fa-terminal"></i> Rest API</a>
<a class="dropdown-item" href="/about" style="font-size: 14px"><i class="fas fa-beer"></i> About</a>
</div>
</li>
{% elif caption == "Feedback" %}
<li class="pr-3">
<a {% if caption== active_page %}style="color: #fff; font-weight: 500;" {% endif %}
data-toggle="modal" data-target="#feedbackModal" href="#"> <i class="fas fa-bullhorn"></i><span class="hideable_caption">&nbsp;Feedback</span></a>
</li>
{% elif caption == "Admin" %}
<li class="dropdown pr-3">
<a {% if caption== active_page %}style="color: #fff; font-weight: 500;" {% endif %}
class="dropdown-toggle" data-toggle="dropdown" href="#"><i class="fas fa-user-shield"></i><span class="hideable_caption">&nbsp;{{
caption }}</span><span class="caret"></span></a>
<div class="dropdown-menu">
{% if current_user | user_has_role('manage_users') %}
<a class="dropdown-item" href="/admin/manage_users" style="font-size: 14px"><i
class="fas fa-users-cog"></i> Manage Users</a>
{% endif %}
<a class="dropdown-item" href="/admin/missing_analyses" style="font-size: 14px"><i
class="fas fa-search"></i> Find Missing Analyses</a>
<a class="dropdown-item" href="/admin/logs" style="font-size: 14px"><i
class="fas fa-exclamation-circle"></i> Logs</a>
</div>
</li>
{% elif caption == "Login" %}
{% if current_user.is_authenticated %}
<li class="dropdown pr-3">
<a {% if caption== active_page %}style="color: #fff; font-weight: 500;" {% endif %}
class="dropdown-toggle" data-toggle="dropdown" href="#"><i class="fas fa-user"></i><span class="hideable_caption">&nbsp;{{
current_user.email|truncate(12) }}</span><span class="caret"></span></a>
<div class="dropdown-menu">
<a class="dropdown-item" href="/user_profile" style="font-size: 14px"><i
class="fas fa-user-cog"></i> Profile</a>
<a class="dropdown-item" href="/logout" style="font-size: 14px"><i class="fas fa-sign-out-alt"></i>
Logout</a>
</div>
</li>
{% else %}
<li>
<a {% if caption== active_page %}style="color: #fff; font-weight: 500;" {% endif %} href="/login"><i
class="fas fa-sign-in-alt"></i><span class="hideable_caption">&nbsp;Login</span></a>
</li>
{{ navbar_item("Home", "/", "home") }}
{% call navbar_dropdown_menu("Database", "tasks") %}
{{ navbar_dropdown_item("/database/browse", "list-ul", "Browse Firmware") }}
{{ navbar_dropdown_item("/database/browse_compare", "copy", "Browse Comparisons") }}
{{ navbar_dropdown_item("/database/search", "search", "Basic Search") }}
{{ navbar_dropdown_item("/database/advanced_search", "search-plus", "Advanced Search") }}
{{ navbar_dropdown_item("/database/graphql", "search-plus", "GraphQL Search") }}
{{ navbar_dropdown_item("/database/binary_search", "search-dollar", "Binary Pattern Search") }}
{{ navbar_dropdown_item("/database/browse_binary_search_history", "search-dollar", "Browse Binary Pattern Search History") }}
{% endcall %}
{{ navbar_item("Upload", "/upload", "upload") }}
{% call navbar_dropdown_menu("Info", "info-circle") %}
{{ navbar_dropdown_item("/statistic", "chart-bar", "Statistics") }}
{{ navbar_dropdown_item("/system_health", "heartbeat", "System") }}
{{ navbar_dropdown_item("/plugins", "puzzle-piece", "Plugins") }}
{{ navbar_dropdown_item("/doc", "terminal", "Rest API") }}
{{ navbar_dropdown_item("/about", "beer", "About") }}
{% endcall %}
{{ navbar_item("Feedback", "#", "bullhorn", options='data-toggle="modal" data-target="#feedbackModal"') }}
{% call navbar_dropdown_menu("Admin", "shield") %}
{% if current_user | user_has_role('manage_users') %}
{{ navbar_dropdown_item("/admin/manage_users", "users-cog", "Manage Users") }}
{% endif %}
{{ navbar_dropdown_item("/admin/missing_analyses", "search", "Find Missing Analyses") }}
{{ navbar_dropdown_item("/admin/logs", "exclamation-circle", "Logs") }}
{% endcall %}
{% if auth_is_enabled() %}
{% if current_user.is_authenticated %}
{% call navbar_dropdown_menu(current_user.email | truncate(12), "user") %}
{{ navbar_dropdown_item("/user_profile", "user-cog", "Profile") }}
{{ navbar_dropdown_item("/logout", "sign-out-alt", "Logout") }}
{% endcall %}
{% else %}
{{ navbar_item("Login", "/login", "sign-in-alt") }}
{% endif %}
{% endif %}
{% else %}
<li class="pr-3">
<a {% if caption== active_page %}style="color: #fff; font-weight: 500;" {% endif %} href={{ href }}>{{
icon|safe }}<span class="hideable_caption">&nbsp;{{ caption }}</span></a>
</li>
{% endif %}
{% endfor %}
</ul>

{# dark mode switch #}
Expand Down Expand Up @@ -209,7 +177,7 @@

<div class="row justify-content-center mb-2">
<div class="col-md-4 text-center">
powered by <a href="https://fkie-cad.github.io/FACT_core/">FACT {{ "" | print_program_version }}</a><br/>
powered by <a href="https://fkie-cad.github.io/FACT_core/">FACT {{ get_fact_version() }}</a><br/>
&copy; <a href="http://www.fkie.fraunhofer.de">Fraunhofer FKIE</a> 2015-2025
</div>
</div>
Expand Down