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

Bump express from 4.18.2 to 4.19.2 in /ui #78

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
43 changes: 36 additions & 7 deletions app/blueprints/strelka.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from collections import defaultdict

from io import BytesIO
from typing import Dict, Tuple
from typing import Dict, Tuple, Union

from flask import Blueprint, current_app, jsonify, request, session, Response
from sqlalchemy import or_, desc, asc, func, case
from sqlalchemy import or_, desc, asc, func, case, cast, String
from sqlalchemy.dialects.postgresql import JSON
from sqlalchemy.orm import joinedload

Expand Down Expand Up @@ -60,7 +60,9 @@ def get_database_status() -> Tuple[Response, int]:

@strelka.route("/upload", methods=["POST"])
@auth_required
def submit_file(user: User) -> Tuple[Response, int]:
def submit_file(
user: User,
) -> Union[tuple[Response, int], tuple[dict[str, Union[Response, str]], int]]:
"""
Submit a file (or hash) to the Strelka file analysis engine and save the analysis results to the database.

Expand All @@ -75,6 +77,7 @@ def submit_file(user: User) -> Tuple[Response, int]:
None.
"""
submitted_hash = ""
total_scanned_with_hits = []

if "file" not in request.files and b"hash" not in request.data:
return (
Expand Down Expand Up @@ -173,7 +176,18 @@ def submit_file(user: User) -> Tuple[Response, int]:
api_key=os.environ.get("VIRUSTOTAL_API_KEY"),
file_hash=scanned_file["scan"]["hash"]["sha256"],
)
total_scanned += 1
total_scanned += 1.0
if scanned_file["enrichment"]["virustotal"] > 0:
total_scanned_with_hits.append(
{
"file_sha256": scanned_file["scan"]["hash"][
"sha256"
],
"positives": scanned_file["enrichment"][
"virustotal"
],
}
)
else:
scanned_file["enrichment"] = {"virustotal": -3}
except Exception as e:
Expand Down Expand Up @@ -240,7 +254,20 @@ def submit_file(user: User) -> Tuple[Response, int]:
db.session.commit()

# Return the analysis results and a 200 status code.
return jsonify(response), 200
return (
jsonify(
{
"file_id": str(new_submission.file_id),
"response": response,
"meta": {
"file_size": int(file_size),
"iocs": list(iocs),
"vt_positives": list(total_scanned_with_hits),
},
}
),
200,
)

# If an exception occurs, log the error and return an error message.
except Exception as e:
Expand Down Expand Up @@ -498,8 +525,10 @@ def view(user: User) -> Tuple[Dict[str, any], int]:
# Apply the search filter if there is a search query
if search_query:
search_filter = or_(
FileSubmission.file_name.like(f"%{search_query}%"),
FileSubmission.submitted_description.like(f"%{search_query}%"),
FileSubmission.file_name.ilike(f"%{search_query}%"),
FileSubmission.submitted_description.ilike(f"%{search_query}%"),
cast(FileSubmission.yara_hits, String).ilike(f"%{search_query}%"),
User.user_cn.ilike(f"%{search_query}%"),
)
base_query = base_query.filter(search_filter)

Expand Down
1,161 changes: 569 additions & 592 deletions app/poetry.lock

Large diffs are not rendered by default.

51 changes: 24 additions & 27 deletions app/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,60 +7,57 @@ authors = [
]

[tool.poetry.dependencies]
python = "^3.8"
Alembic = "1.6.2"
Astroid = "2.9.2"
Attrs = "21.4.0"
Black = "21.12b0"
Certifi = "2023.7.22"
Chardet = "4.0.0"
Click = "8.0.0"
Cffi = "1.14.5"
Cryptography = "42.0.4"
Flask = "^2.2"
Flask-Caching = "1.10.1"
Flask-Cors = "3.0.10"
Flask-Expects-Json = "1.7.0"
Flask-Migrate = "2.7.0"
Flask-Script = "2.0.6"
Flask-SQLAlchemy = "2.5.1"
Paste = "3.5.2"
Alembic = "1.6.2"
SQLAlchemy = "1.4.15"
SQLAlchemy-JSON = "0.4.0"
Psycopg2-Binary = "2.9.4"
grpcio = "1.53.0"
grpcio-tools = "1.41.0"
Cryptography = "41.0.6"
PyJWT = "2.4.0"
PyOpenSSL = "20.0.1"
Click = "8.0.0"
Grpcio = "1.53.2"
Grpcio-tools = "1.41.0"
Isort = "5.10.1"
Mypy-Extensions = "0.4.3"
Pylint = "2.12.2"
Pylint-Flask = "0.6"
Pylint-Flask-SQLAlchemy = "0.2.0"
Pylint-Plugin-Utils = "0.7"
Python-Dotenv = "0.17.1"
Waitress = "2.1.2"
Astroid = "2.9.2"
Attrs = "21.4.0"
Black = "21.12b0"
Certifi = "2023.7.22"
Cffi = "1.14.5"
Chardet = "4.0.0"
Jsonschema = "4.3.3"
Lazy-Object-Proxy = "1.7.1"
Ldap3 = "2.9.1"
Lazy-Object-Proxy = "1.7.1"
Mako = "1.2.2"
MarkupSafe = "^2.0.0"
Mccabe = "0.6.1"
Mypy-Extensions = "0.4.3"
Pathspec = "0.9.0"
Paste = "3.5.2"
Platformdirs = "2.4.1"
Protobuf = "3.18.3"
Psycopg2-Binary = "2.9.4"
Pyasn1 = "0.4.8"
PyJWT = "2.4.0"
PyOpenSSL = "20.0.1"
Pycparser = "2.20"
Pyrsistent = "0.18.0"
python = "^3.8"
Python-Dotenv = "0.17.1"
Python-Dateutil = "2.8.1"
Requests = "2.31.0"
Six = "1.16.0"
SQLAlchemy = "1.4.15"
SQLAlchemy-JSON = "0.4.0"
Toml = "0.10.2"
Tomli = "1.2.3"
Typing-Extensions = "4.0.1"
Urllib3 = "1.26.18"
Waitress = "2.1.2"
Wrapt = "1.13.3"
vt-py = "0.17.5"
vt-py = "0.18.0"


[tool.poetry.dev-dependencies]

Expand Down
103 changes: 37 additions & 66 deletions misc/examples/api_examples.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from typing import Optional

import json
import requests
from typing import Optional
from os import getenv

# Set API key and base URL
api_key = "<API_KEY>"
url_base = "http://0.0.0.0:8080/api/strelka"
# Environment variables for configuration
api_base = getenv("STRELKA_API_BASE", "http://localhost:8081/api/strelka")
api_key = getenv("STRELKA_API_KEY", "")

# Define headers
headers = {
Expand All @@ -13,83 +14,53 @@
"Accept": "application/json",
}


def get_scans_list(page: int = 1, per_page: int = 10) -> Optional[str]:
"""
Get list of scans from the Strelka UI API.

Args:
page (int): The page number to retrieve. Defaults to 1.
per_page (int): The number of items per page. Defaults to 10.

Returns:
Optional[str]: The list of scans as a JSON string if the request is successful, None otherwise.
"""
url_route = f"/scans?page={page}&per_page={per_page}"
# Helper function to handle requests
def make_request(method: str, url: str, **kwargs) -> Optional[str]:
try:
response = requests.get(url_base + url_route, headers=headers)
response = requests.request(method, url, headers=headers, **kwargs)
response.raise_for_status()
return response.text
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e.response.status_code} for {url}: {e.response.text}")
except requests.exceptions.RequestException as e:
print(f"Error: {e}")
return None
print(f"Request Error: {e}")
return None


def get_scan_by_id(scan_id: str) -> Optional[str]:
"""
Get scan details by ID from the Strelka UI API.
# Endpoints
def get_scans_list(page: int = 1, per_page: int = 10) -> Optional[str]:
return make_request("GET", f"{api_base}/scans?page={page}&per_page={per_page}")

Args:
scan_id (str): The ID of the scan to retrieve.

Returns:
Optional[str]: The scan details as a JSON string if the request is successful, None otherwise.
"""
url_route = f"/scans/{scan_id}"
try:
response = requests.get(url_base + url_route, headers=headers)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print(f"Error: {e}")
return None
def get_scan_by_id(scan_id: str) -> Optional[str]:
return make_request("GET", f"{api_base}/scans/{scan_id}")


def upload_file(filename: str, description: str) -> Optional[str]:
"""
Upload a file to the Strelka UI API.
with open(filename, "rb") as f:
files = {"file": (filename, f)}
data = {"description": description}
return make_request("POST", f"{api_base}/upload", files=files, data=data)

Args:
filename (str): The file path of the file to upload.
description (str): The description of the file.

Returns:
Optional[str]: The response as a JSON string if the upload is successful, None otherwise.
def upload_file_to_vt(file_hash: str, description: str) -> Optional[str]:
"""
url_route = "/upload"
upload_headers = {
"X-API-KEY": api_key,
Sample Response:
{"file_id":"07538964-b156-4ed3-93b4-85bcd07b5fbc",
"results":[{"enrichment":{"virustotal":-1},"file":{"depth":0,"flavors":{"mime":["application/zip"],"yara":["encrypted_zip","zip_file"]}...
}

try:
with open(filename, "rb") as f:
file_data = f.read()

files = [("file", (filename, file_data))]
data = {"description": description}

response = requests.post(
url_base + url_route, files=files, data=data, headers=upload_headers
)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print(f"Error: {e}")
return None
"""
payload = json.dumps({"description": description, "hash": file_hash})
return make_request("POST", f"{api_base}/upload", data=payload)


if __name__ == "__main__":
# Example usage
print(get_scans_list())
print(get_scan_by_id("<SCAN_ID>"))
print(upload_file("<FILEPATH_TO_FILE_TO_UPLOAD>", "This is a test file"))
# print(get_scans_list())
# print(get_scan_by_id("<SCAN_ID>"))
# print(upload_file("<FILEPATH_TO_FILE_TO_UPLOAD>", "This is a test file"))
print(
upload_file_to_vt(
"5da8c98136d98dfec4716edd79c7145f", "VirusTotal Upload - Calc.exe"
)
)
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"dagre": "^0.8.5",
"dns-packet": "^5.4.0",
"ejs": "^3.1.8",
"follow-redirects": "1.15.6",
"glob-parent": "^6.0.2",
"html-to-image": "^1.11.11",
"loader-utils": "^2.0.4",
Expand Down
47 changes: 36 additions & 11 deletions ui/src/components/FileComponents/OcrOverviewCard.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import React, { useState } from "react";
import { Checkbox, Input, Row, Col, Modal } from "antd";
import { Checkbox, Input, Row, Col, Modal, Button, Tooltip } from "antd";
import "../../styles/OcrOverviewCard.css";

const OcrOverviewCard = ({ data }) => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [wrapText, setWrapText] = useState(false);
const [trimText, setTrimText] = useState(true);
const [filter, setFilter] = useState("");
const [isBlurred, setIsBlurred] = useState(data.scan.qr ? true : false); // State to manage blur for QR codes

const showModal = () => setIsModalVisible(true);
const showModal = () => {
// Only show modal if the image is not blurred
if (!isBlurred) {
setIsModalVisible(true);
}
};
const handleCancel = () => setIsModalVisible(false);
const toggleBlur = () => setIsBlurred(!isBlurred);

let texts = Array.isArray(data.scan.ocr?.text)
? data.scan.ocr.text
Expand All @@ -27,6 +34,16 @@ const OcrOverviewCard = ({ data }) => {
return <div className="thumbnail-placeholder" />;
};

// Conditional styling for blurred image (QR codes)
const imageStyle = isBlurred
? {
filter: "blur(4px)",
cursor: "pointer",
}
: {
cursor: "pointer",
};

// Function to create line numbers and corresponding text
const renderTextLines = (texts) => {
let lineNumber = 1; // Initialize line number
Expand Down Expand Up @@ -83,19 +100,27 @@ const OcrOverviewCard = ({ data }) => {
</table>
</Col>
<Col span={5} className="thumbnail-container">
{base64Thumbnail ? (
<>
{base64Thumbnail ? (
<div className="thumbnail-wrapper">
<img
src={`data:image/jpeg;base64,${base64Thumbnail}`}
alt="Email Preview"
style={{
width: "auto",
maxHeight: "200px",
overflowY: "auto",
cursor: "pointer",
}}
style={imageStyle}
onClick={showModal}
/>
{isBlurred && (
<div>
<Tooltip title="QR codes can pose a security risk, use with caution">
<Button
onClick={toggleBlur}
danger
className="centered-button"
>
Remove Blur
</Button>
</Tooltip>
</div>
)}
<Modal
open={isModalVisible}
footer={null}
Expand All @@ -107,7 +132,7 @@ const OcrOverviewCard = ({ data }) => {
style={{ width: "100%" }}
/>
</Modal>
</>
</div>
) : (
<ThumbnailPlaceholder />
)}
Expand Down
Loading
Loading