195 lines
5.6 KiB
Python
195 lines
5.6 KiB
Python
"""
|
|
©AngelaMos | 2025
|
|
Coordinates scanners and saves results
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy.orm import Session
|
|
from fastapi import HTTPException, status
|
|
|
|
from ..core.enums import TestType
|
|
from ..repositories.scan_repository import ScanRepository
|
|
from ..repositories.test_result_repository import TestResultRepository
|
|
|
|
from ..scanners.auth_scanner import AuthScanner
|
|
from ..scanners.idor_scanner import IDORScanner
|
|
from ..scanners.sqli_scanner import SQLiScanner
|
|
from ..scanners.rate_limit_scanner import RateLimitScanner
|
|
from ..schemas.test_result_schemas import TestResultCreate
|
|
from ..schemas.scan_schemas import ScanRequest, ScanResponse
|
|
|
|
|
|
class ScanService:
|
|
"""
|
|
Orchestrates security scanning workflow
|
|
"""
|
|
@staticmethod
|
|
def run_scan(
|
|
db: Session,
|
|
user_id: int,
|
|
scan_request: ScanRequest
|
|
) -> ScanResponse:
|
|
"""
|
|
Execute security scan with selected tests
|
|
|
|
Args:
|
|
db: Database session
|
|
user_id: User ID initiating the scan
|
|
scan_request: Scan configuration and tests to run
|
|
|
|
Returns:
|
|
ScanResponse: Scan results with all test outcomes
|
|
"""
|
|
scan = ScanRepository.create_scan(
|
|
db = db,
|
|
user_id = user_id,
|
|
target_url = str(scan_request.target_url),
|
|
)
|
|
|
|
scanner_mapping = {
|
|
TestType.RATE_LIMIT: RateLimitScanner,
|
|
TestType.AUTH: AuthScanner,
|
|
TestType.SQLI: SQLiScanner,
|
|
TestType.IDOR: IDORScanner,
|
|
}
|
|
|
|
results: list[TestResultCreate] = []
|
|
|
|
for test_type in scan_request.tests_to_run:
|
|
scanner_class = scanner_mapping.get(test_type)
|
|
|
|
if not scanner_class:
|
|
continue
|
|
|
|
try:
|
|
scanner = scanner_class(
|
|
target_url = str(scan_request.target_url),
|
|
auth_token = scan_request.auth_token,
|
|
max_requests = scan_request.max_requests,
|
|
)
|
|
|
|
result = scanner.scan()
|
|
results.append(result)
|
|
|
|
except Exception as e:
|
|
results.append(
|
|
TestResultCreate(
|
|
test_name = test_type,
|
|
status = "error",
|
|
severity = "info",
|
|
details = f"Scanner error: {str(e)}",
|
|
evidence_json = {"error": str(e)},
|
|
recommendations_json = [
|
|
"Check target URL is accessible",
|
|
"Verify authentication token if provided",
|
|
],
|
|
)
|
|
)
|
|
|
|
for result in results:
|
|
TestResultRepository.create_test_result(
|
|
db = db,
|
|
scan_id = scan.id,
|
|
test_name = result.test_name,
|
|
status = result.status,
|
|
severity = result.severity,
|
|
details = result.details,
|
|
evidence_json = result.evidence_json,
|
|
recommendations_json = result.recommendations_json,
|
|
)
|
|
|
|
db.refresh(scan)
|
|
|
|
return ScanResponse.model_validate(scan)
|
|
|
|
@staticmethod
|
|
def get_scan_by_id(
|
|
db: Session,
|
|
scan_id: int,
|
|
user_id: int
|
|
) -> ScanResponse:
|
|
"""
|
|
Get scan by ID with authorization check
|
|
|
|
Args:
|
|
db: Database session
|
|
scan_id: Scan ID to retrieve
|
|
user_id: User ID for authorization
|
|
|
|
Returns:
|
|
ScanResponse: Scan data with results
|
|
"""
|
|
scan = ScanRepository.get_by_id(db, scan_id)
|
|
|
|
if not scan:
|
|
raise HTTPException(
|
|
status_code = status.HTTP_404_NOT_FOUND,
|
|
detail = "Scan not found",
|
|
)
|
|
|
|
if scan.user_id != user_id:
|
|
raise HTTPException(
|
|
status_code = status.HTTP_403_FORBIDDEN,
|
|
detail = "Not authorized to access this scan",
|
|
)
|
|
|
|
return ScanResponse.model_validate(scan)
|
|
|
|
@staticmethod
|
|
def get_user_scans(
|
|
db: Session,
|
|
user_id: int,
|
|
skip: int = 0,
|
|
limit: int | None = None
|
|
) -> list[ScanResponse]:
|
|
"""
|
|
Get all scans for a user with pagination
|
|
|
|
Args:
|
|
db: Database session
|
|
user_id: User ID
|
|
skip: Number of records to skip
|
|
limit: Maximum number of records to return
|
|
|
|
Returns:
|
|
list[ScanResponse]: List of user's scans
|
|
"""
|
|
scans = ScanRepository.get_by_user(
|
|
db = db,
|
|
user_id = user_id,
|
|
skip = skip,
|
|
limit = limit
|
|
)
|
|
|
|
return [ScanResponse.model_validate(scan) for scan in scans]
|
|
|
|
@staticmethod
|
|
def delete_scan(db: Session, scan_id: int, user_id: int) -> bool:
|
|
"""
|
|
Delete scan with authorization check
|
|
|
|
Args:
|
|
db: Database session
|
|
scan_id: Scan ID to delete
|
|
user_id: User ID for authorization
|
|
|
|
Returns:
|
|
bool: True if deleted successfully
|
|
"""
|
|
scan = ScanRepository.get_by_id(db, scan_id)
|
|
|
|
if not scan:
|
|
raise HTTPException(
|
|
status_code = status.HTTP_404_NOT_FOUND,
|
|
detail = "Scan not found",
|
|
)
|
|
|
|
if scan.user_id != user_id:
|
|
raise HTTPException(
|
|
status_code = status.HTTP_403_FORBIDDEN,
|
|
detail = "Not authorized to delete this scan",
|
|
)
|
|
|
|
return ScanRepository.delete(db, scan_id)
|