Skip to content

Commit

Permalink
Merge release v2.1.1 into main (#1114)
Browse files Browse the repository at this point in the history
  • Loading branch information
jboddey authored Feb 20, 2025
1 parent 1128502 commit faa6cd3
Show file tree
Hide file tree
Showing 112 changed files with 7,741 additions and 3,562 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
- name: Checkout source
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Download package
uses: actions/download-artifact@v4
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: testrun_package
- name: Install dependencies
Expand Down Expand Up @@ -74,7 +74,7 @@ jobs:
- name: Checkout source
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Download package
uses: actions/download-artifact@v4
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: testrun_package
- name: Install dependencies
Expand Down Expand Up @@ -108,7 +108,7 @@ jobs:
- name: Checkout source
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Download package
uses: actions/download-artifact@v4
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: testrun_package
- name: Install dependencies
Expand Down
4 changes: 0 additions & 4 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@ jobs:
- name: Install Testrun
shell: bash {0}
run: cmd/install -l
- name: Build Testrun
shell: bash {0}
run: cmd/build
timeout-minutes: 10
- name: Run tests for conn module
shell: bash {0}
run: bash testing/unit/run_test_module.sh conn captures ethtool output
Expand Down
111 changes: 110 additions & 1 deletion docs/dev/mockoon.json

Large diffs are not rendered by default.

898 changes: 891 additions & 7 deletions docs/dev/postman.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion docs/get_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ Follow these steps to start Testrun:
1. Start Testrun with the command `sudo testrun`
- To run Testrun in network-only mode (without running any tests), use the `--net-only` option.
- To run Testrun with just one interface (connected to the device), use the `--single-intf` option.


**Note**: Tests that require an internet connection (e.g TLS client, DNS) will produce a non-compliant result.

## Test your device

Expand Down
117 changes: 107 additions & 10 deletions framework/python/src/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
DEVICE_ADDITIONAL_INFO_KEY = "additional_info"

DEVICES_PATH = "local/devices"
PROFILES_PATH = "local/risk_profiles"

RESOURCES_PATH = "resources"
DEVICE_FOLDER_PATH = "devices"
Expand Down Expand Up @@ -133,6 +134,9 @@ def __init__(self, testrun):
self._router.add_api_route("/profiles",
self.delete_profile,
methods=["DELETE"])
self._router.add_api_route("/profile/{profile_name}",
self.export_profile,
methods=["POST"])

# Allow all origins to access the API
origins = ["*"]
Expand Down Expand Up @@ -278,7 +282,8 @@ async def start_testrun(self, request: Request, response: Response):
TestrunStatus.IN_PROGRESS,
TestrunStatus.WAITING_FOR_DEVICE,
TestrunStatus.MONITORING,
TestrunStatus.VALIDATING
TestrunStatus.VALIDATING,
TestrunStatus.STARTING

]:
LOGGER.debug("Testrun is already running. Cannot start another instance")
Expand Down Expand Up @@ -341,7 +346,8 @@ async def stop_testrun(self, response: Response):
not in [TestrunStatus.IN_PROGRESS,
TestrunStatus.WAITING_FOR_DEVICE,
TestrunStatus.MONITORING,
TestrunStatus.VALIDATING]):
TestrunStatus.VALIDATING,
TestrunStatus.STARTING]):
response.status_code = 404
return self._generate_msg(False, "Testrun is not currently running")

Expand All @@ -359,8 +365,9 @@ def shutdown(self, response: Response):
# Check that Testrun is not currently running
if (self._session.get_status()
not in [TestrunStatus.CANCELLED,
TestrunStatus.COMPLIANT,
TestrunStatus.NON_COMPLIANT,
TestrunStatus.PROCEED,
TestrunStatus.DO_NOT_PROCEED,
TestrunStatus.COMPLETE,
TestrunStatus.IDLE
]):
LOGGER.debug("Unable to shutdown Testrun as Testrun is in progress")
Expand Down Expand Up @@ -521,12 +528,14 @@ async def delete_device(self, request: Request, response: Response):
if (self._session.get_target_device() == device
and self._session.get_status()
not in [TestrunStatus.CANCELLED,
TestrunStatus.COMPLIANT,
TestrunStatus.NON_COMPLIANT
TestrunStatus.COMPLETE,
TestrunStatus.PROCEED,
TestrunStatus.DO_NOT_PROCEED
]):

response.status_code = 403
return self._generate_msg(
False, "Cannot delete this device whilst " + "it is being tested")
False, "Cannot delete this device whilst it is being tested")

# Delete device
self._testrun.delete_device(device)
Expand All @@ -540,7 +549,7 @@ async def delete_device(self, request: Request, response: Response):
LOGGER.error(e)
response.status_code = 500
return self._generate_msg(
False, "An error occured whilst deleting " + "the device")
False, "An error occured whilst deleting the device")

async def save_device(self, request: Request, response: Response):
LOGGER.debug("Received device post request")
Expand Down Expand Up @@ -644,8 +653,9 @@ async def edit_device(self, request: Request, response: Response):
if (self._session.get_target_device() == device
and self._session.get_status()
not in [TestrunStatus.CANCELLED,
TestrunStatus.COMPLIANT,
TestrunStatus.NON_COMPLIANT
TestrunStatus.COMPLETE,
TestrunStatus.PROCEED,
TestrunStatus.DO_NOT_PROCEED
]):
response.status_code = 403
return self._generate_msg(
Expand Down Expand Up @@ -926,6 +936,93 @@ async def delete_profile(self, request: Request, response: Response):

return self._generate_msg(True, "Successfully deleted that profile")

async def export_profile(self, request: Request, response: Response,
profile_name):

LOGGER.debug(f"Received get profile request for {profile_name}")

device = None

try:
req_raw = (await request.body()).decode("UTF-8")
req_json = json.loads(req_raw)

# Check if device mac_addr has been specified
if "mac_addr" in req_json and len(req_json.get("mac_addr")) > 0:
device_mac_addr = req_json.get("mac_addr")
device = self.get_session().get_device(device_mac_addr)

# If device is not found return 404
if device is None:
response.status_code = status.HTTP_404_NOT_FOUND
return self._generate_msg(
False, "A device with that mac address could not be found")

except JSONDecodeError:
# Device not specified
pass

# Retrieve the profile
profile = self._session.get_profile(profile_name)

# If the profile not found return 404
if profile is None:
LOGGER.info("Profile not found, returning 404")
response.status_code = 404
return self._generate_msg(False, "Profile could not be found")

# If device has been added into the body
if device:

try:

# Path where the PDF will be saved
profile_pdf_path = os.path.join(PROFILES_PATH, f"{profile_name}.pdf")

# Write the PDF content
with open(profile_pdf_path, "wb") as f:
f.write(profile.to_pdf(device).getvalue())

# Return the pdf file
if os.path.isfile(profile_pdf_path):
return FileResponse(profile_pdf_path)
else:
LOGGER.info("Profile could not be found, returning 404")
response.status_code = 404
return self._generate_msg(False, "Profile could not be found")

# Exceptions if the PDF creation fails
except Exception as e:
LOGGER.error(f"Error creating the profile PDF: {e}")
response.status_code = 500
return self._generate_msg(False, "Error retrieving the profile PDF")

# If device not added into the body
else:

try:

# Path where the PDF will be saved
profile_pdf_path = os.path.join(PROFILES_PATH, f"{profile_name}.pdf")

# Write the PDF content
with open(profile_pdf_path, "wb") as f:
f.write(profile.to_pdf_no_device().getvalue())

# Return the pdf file
if os.path.isfile(profile_pdf_path):
return FileResponse(profile_pdf_path)
else:
LOGGER.info("Profile could not be found, returning 404")
response.status_code = 404
return self._generate_msg(False, "Profile could not be found")

# Exceptions if the PDF creation fails
except Exception as e:
LOGGER.error(f"Error creating the profile PDF: {e}")
response.status_code = 500
return self._generate_msg(False, "Error retrieving the profile PDF")

# Certificates
def get_certs(self):
LOGGER.debug("Received certs list request")
Expand Down
48 changes: 44 additions & 4 deletions framework/python/src/common/risk_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ def to_html(self, device):
logo_img_b64 = base64.b64encode(f.read()).decode('utf-8')

self._device = self._format_device_profile(device)
pages = self._generate_report_pages()
pages = self._generate_report_pages(device)
return self._template.render(
styles=self._template_styles,
manufacturer=self._device.manufacturer,
Expand All @@ -366,7 +366,33 @@ def to_html(self, device):
created_at=self.created.strftime('%d.%m.%Y')
)

def _generate_report_pages(self):
def to_html_no_device(self):
"""Returns the risk profile in HTML format without device info"""

high_risk_message = '''The device has been assessed to be high
risk due to the nature of the answers provided
about the device functionality.'''
limited_risk_message = '''The device has been assessed to be limited risk
due to the nature of the answers provided about
the device functionality.'''

with open(test_run_img_file, 'rb') as f:
logo_img_b64 = base64.b64encode(f.read()).decode('utf-8')

pages = self._generate_report_pages()
return self._template.render(
styles=self._template_styles,
logo=logo_img_b64,
risk=self.risk,
high_risk_message=high_risk_message,
limited_risk_message=limited_risk_message,
pages=pages,
total_pages=len(pages),
version=self.version,
created_at=self.created.strftime('%d.%m.%Y')
)

def _generate_report_pages(self, device=None):

# Text block heght
block_height = 18
Expand All @@ -391,8 +417,11 @@ def _generate_report_pages(self):
current_page = []
index = 1

questions = deepcopy(self._device.additional_info)
questions.extend(self.questions)
questions = deepcopy(self.questions)

if device:
questions = deepcopy(self._device.additional_info)
questions.extend(self.questions)

for question in questions:

Expand Down Expand Up @@ -456,6 +485,17 @@ def to_pdf(self, device):
HTML(string=html).write_pdf(pdf_bytes)
return pdf_bytes

def to_pdf_no_device(self):
"""Returns the risk profile in PDF format without device info"""

# Resolve the data as html first
html = self.to_html_no_device()

# Convert HTML to PDF in memory using weasyprint
pdf_bytes = BytesIO()
HTML(string=html).write_pdf(pdf_bytes)
return pdf_bytes

# Adding risks to device profile questions
def _format_device_profile(self, device):
device_copy = deepcopy(device)
Expand Down
12 changes: 10 additions & 2 deletions framework/python/src/common/statuses.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,26 @@


class TestrunStatus:
"""Statuses for overall testing"""
IDLE = "Idle"
STARTING = "Starting"
WAITING_FOR_DEVICE = "Waiting for Device"
MONITORING = "Monitoring"
IN_PROGRESS = "In Progress"
CANCELLED = "Cancelled"
COMPLIANT = "Compliant"
NON_COMPLIANT = "Non-Compliant"
STOPPING = "Stopping"
VALIDATING = "Validating Network"
COMPLETE = "Complete"
PROCEED = "Proceed"
DO_NOT_PROCEED = "Do Not Proceed"

class TestrunResult:
"""Statuses for the Testrun result"""
COMPLIANT = "Compliant"
NON_COMPLIANT = "Non-Compliant"

class TestResult:
"""Statuses for test results"""
IN_PROGRESS = "In Progress"
COMPLIANT = "Compliant"
NON_COMPLIANT = "Non-Compliant"
Expand Down
Loading

0 comments on commit faa6cd3

Please sign in to comment.