refactor(storage): remove signer, using general file storage

- Removed unused app asset download and upload endpoints, along with sandbox archive and file download endpoints.
- Updated imports in the file controller to reflect the removal of these endpoints.
- Simplified the generator.py file by consolidating the code context field definition.
- Enhanced the storage layer with a unified presign wrapper for better handling of presigned URLs.
This commit is contained in:
Harry
2026-01-29 23:01:12 +08:00
parent 4aea4071a8
commit f52fb919d1
21 changed files with 449 additions and 1087 deletions

View File

@ -1,4 +1,23 @@
"""Storage wrapper that provides presigned URL support with fallback to signed proxy URLs."""
"""Storage wrapper that provides presigned URL support with fallback to signed proxy URLs.
This is the unified presign wrapper for all storage operations. When the underlying
storage backend doesn't support presigned URLs (raises NotImplementedError), it falls
back to generating signed proxy URLs that route through Dify's file proxy endpoints.
Usage:
from extensions.storage.file_presign_storage import FilePresignStorage
# Wrap any BaseStorage to add presign support
presign_storage = FilePresignStorage(base_storage)
download_url = presign_storage.get_download_url("path/to/file.txt", expires_in=3600)
upload_url = presign_storage.get_upload_url("path/to/file.txt", expires_in=3600)
The proxy URLs follow the format:
{FILES_URL}/files/storage/{encoded_filename}/(download|upload)?timestamp=...&nonce=...&sign=...
Signature format:
HMAC-SHA256(SECRET_KEY, "storage-file|{operation}|{filename}|{timestamp}|{nonce}")
"""
import base64
import hashlib
@ -12,59 +31,81 @@ from extensions.storage.storage_wrapper import StorageWrapper
class FilePresignStorage(StorageWrapper):
"""Storage wrapper that provides presigned URL support.
"""Storage wrapper that provides presigned URL support with proxy fallback.
If the wrapped storage supports presigned URLs, delegates to it.
Otherwise, generates signed proxy URLs for download.
Otherwise, generates signed proxy URLs for both download and upload operations.
"""
SIGNATURE_PREFIX = "storage-download"
SIGNATURE_PREFIX = "storage-file"
def get_download_url(self, filename: str, expires_in: int = 3600) -> str:
"""Get a presigned download URL, falling back to proxy URL if not supported."""
try:
return super().get_download_url(filename, expires_in)
return self._storage.get_download_url(filename, expires_in)
except NotImplementedError:
return self._generate_signed_proxy_url(filename, expires_in)
def get_upload_url(self, filename: str, expires_in: int = 3600) -> str:
try:
return super().get_upload_url(filename, expires_in)
except NotImplementedError:
return self._generate_signed_upload_url(filename)
return self._generate_signed_proxy_url(filename, "download", expires_in)
def get_download_urls(self, filenames: list[str], expires_in: int = 3600) -> list[str]:
"""Get presigned download URLs for multiple files."""
try:
return super().get_download_urls(filenames, expires_in)
return self._storage.get_download_urls(filenames, expires_in)
except NotImplementedError:
return [self._generate_signed_proxy_url(filename, expires_in) for filename in filenames]
return [self._generate_signed_proxy_url(f, "download", expires_in) for f in filenames]
def _generate_signed_upload_url(self, filename: str) -> str:
# TODO: Implement this
raise NotImplementedError("This storage backend doesn't support pre-signed URLs")
def get_upload_url(self, filename: str, expires_in: int = 3600) -> str:
"""Get a presigned upload URL, falling back to proxy URL if not supported."""
try:
return self._storage.get_upload_url(filename, expires_in)
except NotImplementedError:
return self._generate_signed_proxy_url(filename, "upload", expires_in)
def _generate_signed_proxy_url(self, filename: str, expires_in: int = 3600) -> str:
def _generate_signed_proxy_url(self, filename: str, operation: str, expires_in: int = 3600) -> str:
"""Generate a signed proxy URL for file operations.
Args:
filename: The storage key/path
operation: Either "download" or "upload"
expires_in: URL validity duration in seconds
Returns:
Signed proxy URL string
"""
base_url = dify_config.FILES_URL
encoded_filename = urllib.parse.quote(filename, safe="")
url = f"{base_url}/files/storage/{encoded_filename}/download"
url = f"{base_url}/files/storage/{encoded_filename}/{operation}"
timestamp = str(int(time.time()))
nonce = os.urandom(16).hex()
sign = self._create_signature(filename, timestamp, nonce)
sign = self._create_signature(operation, filename, timestamp, nonce)
query = urllib.parse.urlencode({"timestamp": timestamp, "nonce": nonce, "sign": sign})
return f"{url}?{query}"
@classmethod
def _create_signature(cls, filename: str, timestamp: str, nonce: str) -> str:
def _create_signature(cls, operation: str, filename: str, timestamp: str, nonce: str) -> str:
"""Create HMAC signature for the proxy URL."""
key = dify_config.SECRET_KEY.encode()
msg = f"{cls.SIGNATURE_PREFIX}|{filename}|{timestamp}|{nonce}"
msg = f"{cls.SIGNATURE_PREFIX}|{operation}|{filename}|{timestamp}|{nonce}"
sign = hmac.new(key, msg.encode(), hashlib.sha256).digest()
return base64.urlsafe_b64encode(sign).decode()
@classmethod
def verify_signature(cls, *, filename: str, timestamp: str, nonce: str, sign: str) -> bool:
expected_sign = cls._create_signature(filename, timestamp, nonce)
if sign != expected_sign:
def verify_signature(cls, *, operation: str, filename: str, timestamp: str, nonce: str, sign: str) -> bool:
"""Verify the signature of a proxy URL.
Args:
operation: The operation type ("download" or "upload")
filename: The storage key/path
timestamp: Unix timestamp string from the URL
nonce: Random nonce string from the URL
sign: Signature string from the URL
Returns:
True if signature is valid and not expired, False otherwise
"""
expected_sign = cls._create_signature(operation, filename, timestamp, nonce)
if not hmac.compare_digest(sign, expected_sign):
return False
current_time = int(time.time())