security: harden connector policy and add dashboard widget
This commit is contained in:
@@ -348,8 +348,8 @@ def _file_connector_policy_resource_id(scope_type: str, scope_id: str | None) ->
|
||||
return f"{scope_type.strip().casefold()}:{scope_id or ''}"
|
||||
|
||||
|
||||
async def _read_limited_upload(upload: UploadFile, *, max_bytes: int) -> bytes:
|
||||
data = await upload.read(max_bytes + 1)
|
||||
def _read_limited_upload(upload: UploadFile, *, max_bytes: int) -> bytes:
|
||||
data = upload.file.read(max_bytes + 1)
|
||||
if len(data) > max_bytes:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
||||
@@ -358,12 +358,12 @@ async def _read_limited_upload(upload: UploadFile, *, max_bytes: int) -> bytes:
|
||||
return data
|
||||
|
||||
|
||||
async def _spool_limited_upload_to_temp(upload: UploadFile, *, max_bytes: int, suffix: str = ".upload") -> str:
|
||||
def _spool_limited_upload_to_temp(upload: UploadFile, *, max_bytes: int, suffix: str = ".upload") -> str:
|
||||
tmp = tempfile.NamedTemporaryFile(prefix="govoplan-upload-", suffix=suffix, delete=False)
|
||||
total = 0
|
||||
try:
|
||||
while True:
|
||||
chunk = await upload.read(1024 * 1024)
|
||||
chunk = upload.file.read(1024 * 1024)
|
||||
if not chunk:
|
||||
break
|
||||
total += len(chunk)
|
||||
@@ -1205,7 +1205,7 @@ def _cursor_page_size(scope: str, cursor: str | None, explicit_page_size: int |
|
||||
if explicit_page_size is not None:
|
||||
return explicit_page_size
|
||||
if not cursor:
|
||||
return None
|
||||
return DEFAULT_FILE_LIST_PAGE_SIZE
|
||||
try:
|
||||
values = decode_keyset_cursor(scope, cursor)
|
||||
except KeysetCursorError as exc:
|
||||
@@ -2094,7 +2094,7 @@ def list_files(
|
||||
|
||||
|
||||
@router.post("/upload", response_model=FileUploadResponse)
|
||||
async def upload_files(
|
||||
def upload_files(
|
||||
files: list[UploadFile] = FastAPIFile(...),
|
||||
owner_type: Literal["user", "group"] = Form(default="user"),
|
||||
owner_id: str | None = Form(default=None),
|
||||
@@ -2121,7 +2121,7 @@ async def upload_files(
|
||||
content_type = upload.content_type or None
|
||||
upload_limit = settings.file_upload_zip_max_bytes if unpack_zip and filename.lower().endswith(".zip") else settings.file_upload_max_bytes
|
||||
if unpack_zip and filename.lower().endswith(".zip"):
|
||||
zip_path = await _spool_limited_upload_to_temp(upload, max_bytes=upload_limit, suffix=".zip")
|
||||
zip_path = _spool_limited_upload_to_temp(upload, max_bytes=upload_limit, suffix=".zip")
|
||||
try:
|
||||
extracted = extract_zip_upload(
|
||||
session,
|
||||
@@ -2143,7 +2143,7 @@ async def upload_files(
|
||||
_cleanup_temp_file(zip_path)
|
||||
uploaded_assets.extend(item.asset for item in extracted)
|
||||
continue
|
||||
data = await _read_limited_upload(upload, max_bytes=upload_limit)
|
||||
data = _read_limited_upload(upload, max_bytes=upload_limit)
|
||||
stored = create_file_asset(
|
||||
session,
|
||||
tenant_id=principal.tenant_id,
|
||||
@@ -2173,7 +2173,7 @@ async def upload_files(
|
||||
|
||||
|
||||
@router.post("/upload-zip", response_model=FileUploadResponse)
|
||||
async def upload_zip(
|
||||
def upload_zip(
|
||||
file: UploadFile = FastAPIFile(...),
|
||||
owner_type: Literal["user", "group"] = Form(default="user"),
|
||||
owner_id: str | None = Form(default=None),
|
||||
@@ -2194,7 +2194,7 @@ async def upload_zip(
|
||||
upload_resolutions = _conflict_resolutions([ConflictResolutionRequest(**item) for item in raw_resolutions])
|
||||
_enforce_connector_policy(source_provenance_json, connector_policy_json, operation="import")
|
||||
metadata = _source_metadata_from_form(source_provenance_json, source_revision)
|
||||
zip_path = await _spool_limited_upload_to_temp(file, max_bytes=settings.file_upload_zip_max_bytes, suffix=".zip")
|
||||
zip_path = _spool_limited_upload_to_temp(file, max_bytes=settings.file_upload_zip_max_bytes, suffix=".zip")
|
||||
extracted = extract_zip_upload(
|
||||
session,
|
||||
tenant_id=principal.tenant_id,
|
||||
|
||||
Reference in New Issue
Block a user