From 53f8ded658e6a475ded71455b09693b3143bffbd Mon Sep 17 00:00:00 2001 From: adamhaley Date: Mon, 18 Aug 2025 17:07:06 -0700 Subject: [PATCH] updates for keyring.alt and fallbacks on RUnPod or environments where keyring is not available --- dropbox_auth_manager.py | 124 +++++++++++++++++++++++++++++++++++----- setup_dropbox_node.py | 26 +++++++-- 2 files changed, 131 insertions(+), 19 deletions(-) diff --git a/dropbox_auth_manager.py b/dropbox_auth_manager.py index b18c7b7..76abe82 100644 --- a/dropbox_auth_manager.py +++ b/dropbox_auth_manager.py @@ -6,16 +6,109 @@ from keyring.errors import KeyringError DROPBOX_KEYRING_SERVICE = "comfyui_dropbox" +def setup_keyring_backend(): + """Setup the best available keyring backend for the environment""" + try: + # Try to use the default backend first + keyring.get_keyring() + test_service = "test_keyring_availability" + keyring.set_password(test_service, "test", "test") + keyring.delete_password(test_service, "test") + print("[DropboxAuthManager] Using system keyring backend") + return True + except Exception as e: + print(f"[DropboxAuthManager] System keyring not available: {e}") + + # Try keyrings.alt file-based backends + try: + import keyrings.alt.file + import keyrings.alt.cryptfile + + # Try encrypted file keyring first (more secure) + try: + crypto_keyring = keyrings.alt.cryptfile.CryptFileKeyring() + # Set a simple password for the keyring file + crypto_keyring.keyring_key = "comfyui_dropbox_keyring_2024" + + # Set custom file location in the plugin directory + import os + plugin_dir = os.path.dirname(os.path.abspath(__file__)) + crypto_keyring.filename = os.path.join(plugin_dir, ".dropbox_keyring.cfg") + + keyring.set_keyring(crypto_keyring) + + # Test it works + test_service = "test_crypto_keyring" + keyring.set_password(test_service, "test", "test") + keyring.delete_password(test_service, "test") + + print(f"[DropboxAuthManager] Using encrypted file keyring: {crypto_keyring.filename}") + return True + + except Exception as crypto_error: + print(f"[DropboxAuthManager] Encrypted file keyring failed: {crypto_error}") + + # Fall back to plaintext file keyring + try: + file_keyring = keyrings.alt.file.PlaintextKeyring() + + # Set custom file location in the plugin directory + plugin_dir = os.path.dirname(os.path.abspath(__file__)) + file_keyring.filename = os.path.join(plugin_dir, ".dropbox_keyring_plaintext.cfg") + + keyring.set_keyring(file_keyring) + + # Test it works + test_service = "test_file_keyring" + keyring.set_password(test_service, "test", "test") + keyring.delete_password(test_service, "test") + + print(f"[DropboxAuthManager] Using plaintext file keyring: {file_keyring.filename}") + print(f"[DropboxAuthManager] WARNING: Credentials will be stored in plaintext") + return True + + except Exception as file_error: + print(f"[DropboxAuthManager] File keyring also failed: {file_error}") + return False + + except ImportError: + print("[DropboxAuthManager] keyrings.alt not available - install with: pip install keyrings.alt") + return False + + except Exception as e: + print(f"[DropboxAuthManager] All keyring backends failed: {e}") + return False + class DropboxAuthManager: def __init__(self, app_key=None, app_secret=None): - self.app_key = app_key if app_key is not None else keyring.get_password(DROPBOX_KEYRING_SERVICE, "app_key") - self.app_secret = app_secret if app_secret is not None else keyring.get_password(DROPBOX_KEYRING_SERVICE, "app_secret") - self.refresh_token = keyring.get_password(DROPBOX_KEYRING_SERVICE, "refresh_token") + # Setup the best available keyring backend first + self.keyring_available = setup_keyring_backend() + + # Try to get from keyring, but handle backend unavailability gracefully + if self.keyring_available: + try: + self.app_key = app_key if app_key is not None else keyring.get_password(DROPBOX_KEYRING_SERVICE, "app_key") + self.app_secret = app_secret if app_secret is not None else keyring.get_password(DROPBOX_KEYRING_SERVICE, "app_secret") + self.refresh_token = keyring.get_password(DROPBOX_KEYRING_SERVICE, "refresh_token") + except Exception as e: + print(f"[DropboxAuthManager] Error reading from keyring: {e}") + self.app_key = app_key + self.app_secret = app_secret + self.refresh_token = None + self.keyring_available = False + else: + print(f"[DropboxAuthManager] No keyring backend available - using provided credentials only") + self.app_key = app_key + self.app_secret = app_secret + self.refresh_token = None def is_connected(self): return bool(self.app_key and self.app_secret and self.refresh_token) def store_tokens(self, app_key, app_secret, refresh_token): + if not self.keyring_available: + raise RuntimeError("Keyring not available. Please use 'env_file' or 'display_only' storage method instead.") + try: keyring.set_password(DROPBOX_KEYRING_SERVICE, "app_key", app_key) keyring.set_password(DROPBOX_KEYRING_SERVICE, "app_secret", app_secret) @@ -126,18 +219,19 @@ class DropboxAuthManager: def reset(self): """Clear stored tokens""" - try: - keyring.delete_password(DROPBOX_KEYRING_SERVICE, "app_key") - keyring.delete_password(DROPBOX_KEYRING_SERVICE, "app_secret") - keyring.delete_password(DROPBOX_KEYRING_SERVICE, "refresh_token") - - # Clear instance variables - self.app_key = None - self.app_secret = None - self.refresh_token = None - except KeyringError: - # If passwords don't exist, that's fine - pass + if self.keyring_available: + try: + keyring.delete_password(DROPBOX_KEYRING_SERVICE, "app_key") + keyring.delete_password(DROPBOX_KEYRING_SERVICE, "app_secret") + keyring.delete_password(DROPBOX_KEYRING_SERVICE, "refresh_token") + except KeyringError: + # If passwords don't exist, that's fine + pass + + # Clear instance variables regardless of keyring availability + self.app_key = None + self.app_secret = None + self.refresh_token = None def get_oauth_url(self, redirect_uri=None, state=None): """Generate OAuth URL for initial authorization""" diff --git a/setup_dropbox_node.py b/setup_dropbox_node.py index ac23f2c..3186c7d 100644 --- a/setup_dropbox_node.py +++ b/setup_dropbox_node.py @@ -18,6 +18,10 @@ class DropboxSetupNode: auth_manager = DropboxAuthManager() is_connected = auth_manager.is_connected() + # If keyring is not available, we know keyring storage won't work + if not auth_manager.keyring_available: + print(f"[DropboxSetup] Keyring not available in this environment") + # Also check for .env file credentials if not is_connected: node_dir = os.path.dirname(__file__) @@ -80,9 +84,17 @@ class DropboxSetupNode: "label": "Automatic OAuth (no manual auth code needed)", "default": True }) - inputs["optional"]["storage_method"] = (["keyring", "env_file", "display_only"], { + # Smart default based on keyring availability + if auth_manager.keyring_available: + default_storage = "keyring" + storage_options = ["keyring", "env_file", "display_only"] + else: + default_storage = "display_only" # Better for RunPod/Docker + storage_options = ["display_only", "env_file", "keyring"] + + inputs["optional"]["storage_method"] = (storage_options, { "label": "Credential Storage Method", - "default": "keyring" + "default": default_storage }) return inputs @@ -284,8 +296,14 @@ class DropboxSetupNode: # Handle different storage methods if storage_method == "keyring": # Store in system keyring (original behavior) - auth_manager_setup.store_tokens(app_key_clean, app_secret_clean, refresh_token) - message = "āœ… Dropbox connected successfully! Credentials stored securely in system keyring." + try: + auth_manager_setup.store_tokens(app_key_clean, app_secret_clean, refresh_token) + message = "āœ… Dropbox connected successfully! Credentials stored securely in system keyring." + except RuntimeError as e: + if "Keyring not available" in str(e): + message = f"āŒ All keyring backends failed in this environment.\n\nšŸ’” Please use 'env_file' or 'display_only' storage method instead.\n\nšŸ”§ To enable file-based keyring, ensure 'keyrings.alt' is installed.\n\nTechnical details: {e}" + else: + raise e elif storage_method == "env_file": # Store in .env file