Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(system-server): add /system/oem_mode/upload_splash endpoint to change boot splash #14865

Merged
merged 16 commits into from
Apr 12, 2024
Merged
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
1 change: 1 addition & 0 deletions system-server/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pydantic = "==1.10.12"
importlib-metadata = ">=4.13.0,<5"
sqlalchemy = "==1.4.51"
pyjwt = "==2.6.0"
filetype = "==1.2.0"
systemd-python = { version = "==234", markers="sys_platform == 'linux'" }
server-utils = {editable = true, path = "./../server-utils"}
system_server = {path = ".", editable = true}
Expand Down
14 changes: 11 additions & 3 deletions system-server/Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 96 additions & 1 deletion system-server/system_server/system/oem_mode/router.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
"""Router for /system/register endpoint."""

from fastapi import APIRouter, Depends, status, Response
import os
import filetype # type: ignore[import-untyped]
from fastapi import (
APIRouter,
Depends,
status,
Response,
UploadFile,
File,
HTTPException,
)

from .models import EnableOEMMode
from ...settings import SystemServerSettings, get_settings, save_settings

Expand Down Expand Up @@ -35,3 +46,87 @@ async def enable_oem_mode_endpoint(
except Exception:
response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
return response


@oem_mode_router.post(
"/system/oem_mode/upload_splash",
summary="Upload an image to be used as the boot up splash screen.",
responses={
status.HTTP_201_CREATED: {"message": "OEM Mode splash screen uploaded"},
status.HTTP_400_BAD_REQUEST: {"message": "OEM Mode splash screen not set"},
status.HTTP_413_REQUEST_ENTITY_TOO_LARGE: {
"message": "File is larger than 5mb"
},
status.HTTP_415_UNSUPPORTED_MEDIA_TYPE: {"message": "Invalid file type"},
status.HTTP_500_INTERNAL_SERVER_ERROR: {
"message": "OEM Mode splash unhandled exception."
Comment on lines +55 to +62
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status responses all make sense and are error codes in line with what would reasonably be expected on receiving end.

},
},
)
async def upload_splash_image(
response: Response,
file: UploadFile = File(...),
settings: SystemServerSettings = Depends(get_settings),
) -> Response:
"""Router for /system/oem_mode/upload_splash endpoint."""
# Make sure oem mode is enabled before this request
if not settings.oem_mode_enabled:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="OEM Mode needs to be enabled to upload splash image.",
)

# Get the file info
file_info = filetype.guess(file.file)
if file_info is None:
raise HTTPException(
status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
detail="Unable to determine file type",
)

# Only accept PNG files
accepted_file_types = ["image/png", "png"]
content_type = file_info.extension.lower()
if (
file.content_type not in accepted_file_types
or content_type not in accepted_file_types
):
raise HTTPException(
status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
detail="Unsupported file type",
)

file_size = 0
for chunk in file.file:
file_size += len(chunk)
if file_size > 5 * 1024 * 1024: # 5MB
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="File is larger than 5mb.",
)

# TODO: Validate image dimensions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO can be removed since we will no longer be validating dimensions on this side.


# return the pointer back to the starting point so that the next read starts from the starting point
await file.seek(0)

try:
# Remove the old image if exists
if settings.oem_mode_splash_custom:
os.unlink(settings.oem_mode_splash_custom)

# file is valid, save to final location
filepath = f"{settings.persistence_directory}/{file.filename}"
with open(filepath, "wb+") as f:
f.write(file.file.read())

# store the file location to settings and save the dotenv
settings.oem_mode_splash_custom = filepath
success = save_settings(settings)
response.status_code = (
status.HTTP_201_CREATED if success else status.HTTP_400_BAD_REQUEST
)
except Exception:
response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR

return response
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 88 additions & 1 deletion system-server/tests/integration/test_oem_mode.tavern.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
test_name: PUT Enable OEM Mode
test_name: Test enable/disable OEM Mode
marks:
- usefixtures:
- run_server
Expand Down Expand Up @@ -34,4 +34,91 @@ stages:
content-type: application/json
response:
status_code: 422
---
test_name: Upload, and validate a good image for OEM Mode

marks:
- usefixtures:
- run_server
stages:
- name: Enable OEM Mode
request:
url: "{host:s}:{port:d}/system/oem_mode/enable"
method: PUT
json:
"enable": true
- name: Upload PNG Image
request: &upload_splash_first
url: "{host:s}:{port:d}/system/oem_mode/upload_splash"
method: POST
files:
file: 'tests/integration/resources/oem_mode_custom.png'
response:
status_code: 201

---
test_name: Dont process upload_splash request if oem mode is disabled

marks:
- usefixtures:
- run_server

stages:
- name: Disable OEM Mode
request:
url: "{host:s}:{port:d}/system/oem_mode/enable"
method: PUT
json:
"enable": false
- name: Upload PNG Image
request:
url: "{host:s}:{port:d}/system/oem_mode/upload_splash"
method: POST
files:
file: 'tests/integration/resources/oem_mode_custom.png'
response:
status_code: 403
- name: Enable OEM Mode
request:
url: "{host:s}:{port:d}/system/oem_mode/enable"
method: PUT
json:
"enable": true
- name: Upload PNG Image
request:
url: "{host:s}:{port:d}/system/oem_mode/upload_splash"
method: POST
files:
file: 'tests/integration/resources/oem_mode_custom.png'
response:
status_code: 201
---
test_name: Validate the image before processing

marks:
- usefixtures:
- run_server

stages:
- name: Enable OEM Mode
request:
url: "{host:s}:{port:d}/system/oem_mode/enable"
method: PUT
json:
"enable": true
- name: Upload non-PNG Image
request:
url: "{host:s}:{port:d}/system/oem_mode/upload_splash"
method: POST
files:
file: 'tests/integration/resources/oem_mode_wrong_image_type.jpeg'
response:
status_code: 415
- name: Upload a PNG Image
request:
url: "{host:s}:{port:d}/system/oem_mode/upload_splash"
method: POST
files:
file: 'tests/integration/resources/oem_mode_custom.png'
response:
status_code: 201
Loading