system_oauth_encryption.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import base64
  2. import hashlib
  3. import logging
  4. from collections.abc import Mapping
  5. from typing import Any, Optional
  6. from Crypto.Cipher import AES
  7. from Crypto.Random import get_random_bytes
  8. from Crypto.Util.Padding import pad, unpad
  9. from pydantic import TypeAdapter
  10. from configs import dify_config
  11. logger = logging.getLogger(__name__)
  12. class OAuthEncryptionError(Exception):
  13. """OAuth encryption/decryption specific error"""
  14. pass
  15. class SystemOAuthEncrypter:
  16. """
  17. A simple OAuth parameters encrypter using AES-CBC encryption.
  18. This class provides methods to encrypt and decrypt OAuth parameters
  19. using AES-CBC mode with a key derived from the application's SECRET_KEY.
  20. """
  21. def __init__(self, secret_key: Optional[str] = None):
  22. """
  23. Initialize the OAuth encrypter.
  24. Args:
  25. secret_key: Optional secret key. If not provided, uses dify_config.SECRET_KEY
  26. Raises:
  27. ValueError: If SECRET_KEY is not configured or empty
  28. """
  29. secret_key = secret_key or dify_config.SECRET_KEY or ""
  30. # Generate a fixed 256-bit key using SHA-256
  31. self.key = hashlib.sha256(secret_key.encode()).digest()
  32. def encrypt_oauth_params(self, oauth_params: Mapping[str, Any]) -> str:
  33. """
  34. Encrypt OAuth parameters.
  35. Args:
  36. oauth_params: OAuth parameters dictionary, e.g., {"client_id": "xxx", "client_secret": "xxx"}
  37. Returns:
  38. Base64-encoded encrypted string
  39. Raises:
  40. OAuthEncryptionError: If encryption fails
  41. ValueError: If oauth_params is invalid
  42. """
  43. try:
  44. # Generate random IV (16 bytes)
  45. iv = get_random_bytes(16)
  46. # Create AES cipher (CBC mode)
  47. cipher = AES.new(self.key, AES.MODE_CBC, iv)
  48. # Encrypt data
  49. padded_data = pad(TypeAdapter(dict).dump_json(dict(oauth_params)), AES.block_size)
  50. encrypted_data = cipher.encrypt(padded_data)
  51. # Combine IV and encrypted data
  52. combined = iv + encrypted_data
  53. # Return base64 encoded string
  54. return base64.b64encode(combined).decode()
  55. except Exception as e:
  56. raise OAuthEncryptionError(f"Encryption failed: {str(e)}") from e
  57. def decrypt_oauth_params(self, encrypted_data: str) -> Mapping[str, Any]:
  58. """
  59. Decrypt OAuth parameters.
  60. Args:
  61. encrypted_data: Base64-encoded encrypted string
  62. Returns:
  63. Decrypted OAuth parameters dictionary
  64. Raises:
  65. OAuthEncryptionError: If decryption fails
  66. ValueError: If encrypted_data is invalid
  67. """
  68. if not isinstance(encrypted_data, str):
  69. raise ValueError("encrypted_data must be a string")
  70. if not encrypted_data:
  71. raise ValueError("encrypted_data cannot be empty")
  72. try:
  73. # Base64 decode
  74. combined = base64.b64decode(encrypted_data)
  75. # Check minimum length (IV + at least one AES block)
  76. if len(combined) < 32: # 16 bytes IV + 16 bytes minimum encrypted data
  77. raise ValueError("Invalid encrypted data format")
  78. # Separate IV and encrypted data
  79. iv = combined[:16]
  80. encrypted_data_bytes = combined[16:]
  81. # Create AES cipher
  82. cipher = AES.new(self.key, AES.MODE_CBC, iv)
  83. # Decrypt data
  84. decrypted_data = cipher.decrypt(encrypted_data_bytes)
  85. unpadded_data = unpad(decrypted_data, AES.block_size)
  86. # Parse JSON
  87. oauth_params: Mapping[str, Any] = TypeAdapter(Mapping[str, Any]).validate_json(unpadded_data)
  88. if not isinstance(oauth_params, dict):
  89. raise ValueError("Decrypted data is not a valid dictionary")
  90. return oauth_params
  91. except Exception as e:
  92. raise OAuthEncryptionError(f"Decryption failed: {str(e)}") from e
  93. # Factory function for creating encrypter instances
  94. def create_system_oauth_encrypter(secret_key: Optional[str] = None) -> SystemOAuthEncrypter:
  95. """
  96. Create an OAuth encrypter instance.
  97. Args:
  98. secret_key: Optional secret key. If not provided, uses dify_config.SECRET_KEY
  99. Returns:
  100. SystemOAuthEncrypter instance
  101. """
  102. return SystemOAuthEncrypter(secret_key=secret_key)
  103. # Global encrypter instance (for backward compatibility)
  104. _oauth_encrypter: Optional[SystemOAuthEncrypter] = None
  105. def get_system_oauth_encrypter() -> SystemOAuthEncrypter:
  106. """
  107. Get the global OAuth encrypter instance.
  108. Returns:
  109. SystemOAuthEncrypter instance
  110. """
  111. global _oauth_encrypter
  112. if _oauth_encrypter is None:
  113. _oauth_encrypter = SystemOAuthEncrypter()
  114. return _oauth_encrypter
  115. # Convenience functions for backward compatibility
  116. def encrypt_system_oauth_params(oauth_params: Mapping[str, Any]) -> str:
  117. """
  118. Encrypt OAuth parameters using the global encrypter.
  119. Args:
  120. oauth_params: OAuth parameters dictionary
  121. Returns:
  122. Base64-encoded encrypted string
  123. """
  124. return get_system_oauth_encrypter().encrypt_oauth_params(oauth_params)
  125. def decrypt_system_oauth_params(encrypted_data: str) -> Mapping[str, Any]:
  126. """
  127. Decrypt OAuth parameters using the global encrypter.
  128. Args:
  129. encrypted_data: Base64-encoded encrypted string
  130. Returns:
  131. Decrypted OAuth parameters dictionary
  132. """
  133. return get_system_oauth_encrypter().decrypt_oauth_params(encrypted_data)