| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- import base64
- import hashlib
- import hmac
- import os
- import time
- from configs import dify_config
- def get_signed_file_url(upload_file_id: str) -> str:
- url = f"{dify_config.FILES_URL}/files/{upload_file_id}/file-preview"
- timestamp = str(int(time.time()))
- nonce = os.urandom(16).hex()
- key = dify_config.SECRET_KEY.encode()
- msg = f"file-preview|{upload_file_id}|{timestamp}|{nonce}"
- sign = hmac.new(key, msg.encode(), hashlib.sha256).digest()
- encoded_sign = base64.urlsafe_b64encode(sign).decode()
- return f"{url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}"
- def get_signed_file_url_for_plugin(filename: str, mimetype: str, tenant_id: str, user_id: str) -> str:
- # Plugin access should use internal URL for Docker network communication
- base_url = dify_config.INTERNAL_FILES_URL or dify_config.FILES_URL
- url = f"{base_url}/files/upload/for-plugin"
- if user_id is None:
- user_id = "DEFAULT-USER"
- timestamp = str(int(time.time()))
- nonce = os.urandom(16).hex()
- key = dify_config.SECRET_KEY.encode()
- msg = f"upload|{filename}|{mimetype}|{tenant_id}|{user_id}|{timestamp}|{nonce}"
- sign = hmac.new(key, msg.encode(), hashlib.sha256).digest()
- encoded_sign = base64.urlsafe_b64encode(sign).decode()
- return f"{url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}&user_id={user_id}&tenant_id={tenant_id}"
- def verify_plugin_file_signature(
- *, filename: str, mimetype: str, tenant_id: str, user_id: str | None, timestamp: str, nonce: str, sign: str
- ) -> bool:
- if user_id is None:
- user_id = "DEFAULT-USER"
- data_to_sign = f"upload|{filename}|{mimetype}|{tenant_id}|{user_id}|{timestamp}|{nonce}"
- secret_key = dify_config.SECRET_KEY.encode()
- recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
- recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode()
- # verify signature
- if sign != recalculated_encoded_sign:
- return False
- current_time = int(time.time())
- return current_time - int(timestamp) <= dify_config.FILES_ACCESS_TIMEOUT
- def verify_image_signature(*, upload_file_id: str, timestamp: str, nonce: str, sign: str) -> bool:
- data_to_sign = f"image-preview|{upload_file_id}|{timestamp}|{nonce}"
- secret_key = dify_config.SECRET_KEY.encode()
- recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
- recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode()
- # verify signature
- if sign != recalculated_encoded_sign:
- return False
- current_time = int(time.time())
- return current_time - int(timestamp) <= dify_config.FILES_ACCESS_TIMEOUT
- def verify_file_signature(*, upload_file_id: str, timestamp: str, nonce: str, sign: str) -> bool:
- data_to_sign = f"file-preview|{upload_file_id}|{timestamp}|{nonce}"
- secret_key = dify_config.SECRET_KEY.encode()
- recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
- recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode()
- # verify signature
- if sign != recalculated_encoded_sign:
- return False
- current_time = int(time.time())
- return current_time - int(timestamp) <= dify_config.FILES_ACCESS_TIMEOUT
- def get_signed_document_proxy_url(dataset_id: str, document_id: str, filename: str) -> str:
- """Generate signed URL for document file proxy (for KKFileView)"""
- from urllib.parse import quote
- base_url = dify_config.CONSOLE_API_URL or ""
- # Encode filename for URL path
- encoded_filename = quote(filename)
- url = f"{base_url}/console/api/datasets/{dataset_id}/documents/{document_id}/file-proxy/{encoded_filename}"
- timestamp = str(int(time.time()))
- nonce = os.urandom(16).hex()
- key = dify_config.SECRET_KEY.encode()
- msg = f"document-proxy|{dataset_id}|{document_id}|{filename}|{timestamp}|{nonce}"
- sign = hmac.new(key, msg.encode(), hashlib.sha256).digest()
- encoded_sign = base64.urlsafe_b64encode(sign).decode()
- return f"{url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}"
- def verify_document_proxy_signature(
- *, dataset_id: str, document_id: str, filename: str, timestamp: str, nonce: str, sign: str
- ) -> bool:
- """Verify signature for document file proxy"""
- data_to_sign = f"document-proxy|{dataset_id}|{document_id}|{filename}|{timestamp}|{nonce}"
- secret_key = dify_config.SECRET_KEY.encode()
- recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
- recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode()
- # verify signature
- if sign != recalculated_encoded_sign:
- return False
- current_time = int(time.time())
- return current_time - int(timestamp) <= dify_config.FILES_ACCESS_TIMEOUT
- def generate_document_proxy_token(dataset_id: str, document_id: str, filename: str) -> str:
- """Generate a secure token for document file proxy access
- The token encodes all necessary information (dataset_id, document_id, filename,
- timestamp, nonce, and signature) into a single URL-safe base64 string.
- Args:
- dataset_id: Dataset UUID
- document_id: Document UUID
- filename: Original filename
- Returns:
- URL-safe base64-encoded token
- """
- import json
- timestamp = str(int(time.time()))
- nonce = os.urandom(16).hex()
- key = dify_config.SECRET_KEY.encode()
- msg = f"document-proxy|{dataset_id}|{document_id}|{filename}|{timestamp}|{nonce}"
- sign = hmac.new(key, msg.encode(), hashlib.sha256).digest()
- encoded_sign = base64.urlsafe_b64encode(sign).decode()
- # Create payload with all parameters
- payload = {
- "dataset_id": dataset_id,
- "document_id": document_id,
- "filename": filename,
- "timestamp": timestamp,
- "nonce": nonce,
- "sign": encoded_sign,
- }
- # Encode payload as base64 URL-safe string
- token_bytes = json.dumps(payload).encode('utf-8')
- token = base64.urlsafe_b64encode(token_bytes).decode('utf-8').rstrip('=')
- return token
- def verify_document_proxy_token(token: str) -> dict[str, str] | None:
- """Verify and decode a document file proxy token
- Args:
- token: Base64-encoded token from URL
- Returns:
- Dict with verified parameters (dataset_id, document_id, filename) if valid,
- None otherwise
- """
- import json
- try:
- # Add padding if needed
- padding = 4 - (len(token) % 4)
- if padding != 4:
- token += '=' * padding
- # Decode base64 token
- token_bytes = base64.urlsafe_b64decode(token)
- payload = json.loads(token_bytes.decode('utf-8'))
- # Extract parameters
- dataset_id = payload.get("dataset_id")
- document_id = payload.get("document_id")
- filename = payload.get("filename")
- timestamp = payload.get("timestamp")
- nonce = payload.get("nonce")
- sign = payload.get("sign")
- if not all([dataset_id, document_id, filename, timestamp, nonce, sign]):
- return None
- # Verify signature using existing function
- if not verify_document_proxy_signature(
- dataset_id=dataset_id,
- document_id=document_id,
- filename=filename,
- timestamp=timestamp,
- nonce=nonce,
- sign=sign
- ):
- return None
- # Return verified parameters
- return {
- "dataset_id": dataset_id,
- "document_id": document_id,
- "filename": filename,
- }
- except (ValueError, AttributeError, KeyError, json.JSONDecodeError):
- return None
|