From ece9c68eb45d2ec01a1cab0893bdf84563d51334 Mon Sep 17 00:00:00 2001 From: adamhaley Date: Tue, 19 Aug 2025 19:05:18 -0700 Subject: [PATCH] removing keyring behavior --- dropbox_auth_manager.py | 172 +++++++++++----------------------------- oauth_handler.py | 28 +++---- setup_dropbox_node.py | 52 ++++-------- 3 files changed, 73 insertions(+), 179 deletions(-) diff --git a/dropbox_auth_manager.py b/dropbox_auth_manager.py index ca1666c..8b2d204 100644 --- a/dropbox_auth_manager.py +++ b/dropbox_auth_manager.py @@ -1,151 +1,76 @@ # dropbox_auth_manager.py import os import requests -import keyring -from keyring.errors import KeyringError - -DROPBOX_KEYRING_SERVICE = "comfyui_dropbox" - -def setup_keyring_backend(): - """Setup the best available keyring backend for the environment - non-blocking""" - try: - # Try to use the default backend first (non-interactive) - current_keyring = keyring.get_keyring() - keyring_name = current_keyring.__class__.__name__ - - # Skip interactive/password-prompting keyrings during startup - if ('crypt' in keyring_name.lower() or 'gnome' in keyring_name.lower() or - 'encrypted' in keyring_name.lower() or 'password' in keyring_name.lower() or - 'SecretService' in keyring_name or 'Keychain' in keyring_name): - print(f"[DropboxAuthManager] Skipping interactive keyring ({keyring_name}) to avoid blocking") - raise Exception("Interactive keyring detected - using fallback") - - # Test non-interactively with our actual service name to be more thorough - test_service = DROPBOX_KEYRING_SERVICE - test_key = "test_key_interactive_check" - test_value = "test_value" - - print(f"[DropboxAuthManager] Testing keyring ({keyring_name}) for interactive prompts...") - keyring.set_password(test_service, test_key, test_value) - retrieved = keyring.get_password(test_service, test_key) - keyring.delete_password(test_service, test_key) - - if retrieved != test_value: - raise Exception("Keyring test failed - retrieved value doesn't match") - - print(f"[DropboxAuthManager] Using system keyring backend: {keyring_name}") - return True - - except Exception as e: - print(f"[DropboxAuthManager] System keyring not available or interactive: {e}") - - # Force fallback to plaintext file keyring to avoid any interactive prompts - try: - import keyrings.alt.file - - # Use plaintext file keyring (non-interactive, works reliably) - file_keyring = keyrings.alt.file.PlaintextKeyring() - - # Set custom file location in the plugin directory - import os - 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 the file keyring 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] NOTE: Credentials stored in plugin directory (not system keyring)") - return True - - except ImportError as e: - print(f"[DropboxAuthManager] keyrings.alt not available: {e}") - print("[DropboxAuthManager] Install with: pip install keyrings.alt") - return False - except Exception as file_error: - print(f"[DropboxAuthManager] File keyring failed: {file_error}") - return False +import json class DropboxAuthManager: def __init__(self, app_key=None, app_secret=None): - # Don't set up keyring during initialization - do it lazily when needed - self.keyring_available = None # Unknown until first access - self.keyring_tested = False - # Always store provided credentials self.app_key = app_key self.app_secret = app_secret self.refresh_token = None - # Try to load from keyring only if no credentials provided - # Skip during initialization to avoid blocking startup - will load lazily when needed + # Try to load from file storage if no credentials provided if app_key is None or app_secret is None: - pass # Load lazily in is_connected() or when actually needed + self._try_load_from_file() - def _ensure_keyring_setup(self): - """Set up keyring backend only when needed (lazy initialization)""" - if not self.keyring_tested: - self.keyring_available = setup_keyring_backend() - self.keyring_tested = True - return self.keyring_available + def _get_credentials_file_path(self): + """Get path to credentials file""" + plugin_dir = os.path.dirname(os.path.abspath(__file__)) + return os.path.join(plugin_dir, ".dropbox_credentials.json") - def _try_load_from_keyring(self): - """Try to load credentials from keyring, but don't block if unavailable""" + def _try_load_from_file(self): + """Try to load credentials from JSON file""" try: - if self._ensure_keyring_setup(): - original_app_key = self.app_key - original_app_secret = self.app_secret - original_refresh_token = self.refresh_token + credentials_path = self._get_credentials_file_path() + if os.path.exists(credentials_path): + with open(credentials_path, 'r') as f: + creds = json.load(f) if self.app_key is None: - self.app_key = keyring.get_password(DROPBOX_KEYRING_SERVICE, "app_key") + self.app_key = creds.get("app_key") if self.app_secret is None: - self.app_secret = keyring.get_password(DROPBOX_KEYRING_SERVICE, "app_secret") + self.app_secret = creds.get("app_secret") if self.refresh_token is None: - self.refresh_token = keyring.get_password(DROPBOX_KEYRING_SERVICE, "refresh_token") + self.refresh_token = creds.get("refresh_token") - # Debug: Show what was loaded - print(f"[DropboxAuthManager] Keyring load results:") - print(f" app_key: {original_app_key} -> {self.app_key}") - print(f" app_secret: {original_app_secret} -> {self.app_secret}") - print(f" refresh_token: {original_refresh_token} -> {bool(self.refresh_token)}") + print(f"[DropboxAuthManager] Loaded credentials from file: {credentials_path}") except Exception as e: - print(f"[DropboxAuthManager] Could not load from keyring: {e}") - # Don't fail - just continue without keyring credentials + print(f"[DropboxAuthManager] Could not load from credentials file: {e}") def is_connected(self): - # Load from keyring lazily if we don't have credentials yet + # Load from file if we don't have credentials yet if not (self.app_key and self.app_secret and self.refresh_token): - self._try_load_from_keyring() + self._try_load_from_file() return bool(self.app_key and self.app_secret and self.refresh_token) def store_tokens(self, app_key, app_secret, refresh_token): - """Store tokens in keyring with fallback for interactive keyrings""" + """Store tokens in JSON file""" try: - if not self._ensure_keyring_setup(): - raise RuntimeError("Keyring not available. Please use 'env_file' or 'display_only' storage method instead.") + credentials_path = self._get_credentials_file_path() - print(f"[DropboxAuthManager] Storing tokens using keyring...") - keyring.set_password(DROPBOX_KEYRING_SERVICE, "app_key", app_key) - keyring.set_password(DROPBOX_KEYRING_SERVICE, "app_secret", app_secret) - keyring.set_password(DROPBOX_KEYRING_SERVICE, "refresh_token", refresh_token) + credentials = { + "app_key": app_key, + "app_secret": app_secret, + "refresh_token": refresh_token + } + + print(f"[DropboxAuthManager] Storing tokens in file: {credentials_path}") + with open(credentials_path, 'w') as f: + json.dump(credentials, f, indent=2) + + # Set appropriate file permissions (readable only by owner) + os.chmod(credentials_path, 0o600) # Update instance variables after successful storage self.app_key = app_key self.app_secret = app_secret self.refresh_token = refresh_token - print(f"[DropboxAuthManager] Tokens stored successfully in keyring") + print(f"[DropboxAuthManager] Tokens stored successfully in credentials file") - except KeyringError as e: - raise RuntimeError(f"Failed to store Dropbox tokens securely: {e}") except Exception as e: - # Handle cases where keyring setup succeeded but storage fails (e.g., interactive prompts) - error_msg = f"Keyring storage failed - this may be due to interactive keyring prompts: {e}" + error_msg = f"Failed to store Dropbox tokens: {e}" print(f"[DropboxAuthManager] {error_msg}") raise RuntimeError(error_msg) @@ -249,10 +174,10 @@ class DropboxAuthManager: """Clear stored tokens and optionally revoke authorization with Dropbox""" if revoke_token: - # Load credentials from keyring first if we don't have them in memory + # Load credentials from file first if we don't have them in memory if not (self.refresh_token and self.app_key and self.app_secret): print("[DropboxAuthManager] Loading credentials for token revocation...") - self._try_load_from_keyring() + self._try_load_from_file() # First, revoke the token with Dropbox if requested and possible if self.refresh_token and self.app_key and self.app_secret: @@ -288,19 +213,16 @@ class DropboxAuthManager: else: print("[DropboxAuthManager] Skipping token revocation (revoke_token=False)") - # Clear from keyring - but only if keyring is already available (don't force setup) - if self.keyring_tested and 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") - print("[DropboxAuthManager] Cleared credentials from keyring") - except Exception as e: - print(f"[DropboxAuthManager] Could not clear keyring (continuing): {e}") - else: - print("[DropboxAuthManager] Keyring not accessed - will clear file-based keyring manually") + # Clear credentials file + try: + credentials_path = self._get_credentials_file_path() + if os.path.exists(credentials_path): + os.remove(credentials_path) + print(f"[DropboxAuthManager] Removed credentials file: {credentials_path}") + except Exception as e: + print(f"[DropboxAuthManager] Could not remove credentials file: {e}") - # Clear instance variables regardless of keyring availability + # Clear instance variables self.app_key = None self.app_secret = None self.refresh_token = None diff --git a/oauth_handler.py b/oauth_handler.py index 0242c12..7e768c6 100644 --- a/oauth_handler.py +++ b/oauth_handler.py @@ -57,20 +57,7 @@ class OAuthCallbackHandler: # Handle different storage methods success_message = None - if self.storage_method == "keyring": - # Store in system keyring (original behavior) - print(f"[OAuthHandler] Attempting to store tokens in keyring...") - try: - auth_manager.store_tokens(self.app_key, self.app_secret, refresh_token) - print(f"[OAuthHandler] Tokens stored in keyring successfully") - success_message = "✅ Dropbox connected successfully! Credentials stored securely in system keyring." - except Exception as e: - print(f"[OAuthHandler] ERROR storing tokens in keyring: {e}") - # Fall back to display_only if keyring fails - success_message = f"⚠️ Keyring storage failed: {e}. Please use 'display_only' or 'env_file' storage method." - return False, success_message - - elif self.storage_method == "env_file": + if self.storage_method == "env_file": # Store in .env file import os node_dir = os.path.dirname(__file__) @@ -123,9 +110,16 @@ DROPBOX_FOLDER={self.dropbox_folder} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━""" else: - # Fallback to keyring - auth_manager.store_tokens(self.app_key, self.app_secret, refresh_token) - success_message = "✅ Dropbox connected successfully! Credentials stored securely in system keyring." + # Fallback to env_file + import os + node_dir = os.path.dirname(__file__) + env_path = os.path.join(node_dir, ".env") + with open(env_path, "w") as f: + f.write(f"DROPBOX_APP_KEY={self.app_key}\n") + f.write(f"DROPBOX_APP_SECRET={self.app_secret}\n") + f.write(f"DROPBOX_REFRESH_TOKEN={refresh_token}\n") + f.write(f"DROPBOX_FOLDER={self.dropbox_folder}\n") + success_message = "✅ Dropbox connected successfully! Credentials saved to .env file." # Send WebSocket message to trigger ComfyUI refresh (except for display_only) if self.storage_method != "display_only": diff --git a/setup_dropbox_node.py b/setup_dropbox_node.py index ae6ab09..3de63df 100644 --- a/setup_dropbox_node.py +++ b/setup_dropbox_node.py @@ -4,6 +4,7 @@ import os import requests import webbrowser import uuid +import json from dotenv import load_dotenv, dotenv_values import urllib.parse from .dropbox_auth_manager import DropboxAuthManager @@ -13,7 +14,7 @@ from .oauth_handler import OAuthCallbackHandler, get_server_base_url class DropboxSetupNode: @classmethod def _check_credentials_non_blocking(cls): - """Check for credentials without accessing keyring (non-blocking)""" + """Check for credentials without blocking operations""" try: node_dir = os.path.dirname(__file__) @@ -35,13 +36,6 @@ class DropboxSetupNode: ]) if display_only_env_set: return True - - # Check keyring files existence - keyring_files = [".dropbox_keyring.cfg", ".dropbox_keyring_plaintext.cfg"] - for keyring_file in keyring_files: - keyring_path = os.path.join(node_dir, keyring_file) - if os.path.exists(keyring_path) and os.path.getsize(keyring_path) > 0: - return True return False except Exception: @@ -89,13 +83,9 @@ class DropboxSetupNode: "label": "Automatic OAuth (no manual auth code needed)", "default": True }) - # Smart default based on keyring availability - if 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"] + # Simple storage options without keyring complexity + default_storage = "env_file" + storage_options = ["env_file", "display_only"] inputs["optional"]["storage_method"] = (storage_options, { "label": "Credential Storage Method", @@ -145,13 +135,6 @@ class DropboxSetupNode: print(f"[DropboxSetup] Removing display_only marker: {display_marker_path}") os.remove(display_marker_path) - # Clear keyring files manually (avoiding keyring API) - keyring_files = [".dropbox_keyring.cfg", ".dropbox_keyring_plaintext.cfg"] - for keyring_file in keyring_files: - keyring_path = os.path.join(node_dir, keyring_file) - if os.path.exists(keyring_path): - print(f"[DropboxSetup] Removing keyring file: {keyring_file}") - os.remove(keyring_path) # Send WebSocket message to trigger ComfyUI refresh after clearing credentials try: @@ -316,18 +299,7 @@ class DropboxSetupNode: print(f"[DropboxSetup] Using storage method: {storage_method}") # Handle different storage methods - if storage_method == "keyring": - # Store in system keyring (original behavior) - 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": + if storage_method == "env_file": # Store in .env file node_dir = os.path.dirname(__file__) env_path = os.path.join(node_dir, ".env") @@ -365,9 +337,15 @@ DROPBOX_FOLDER={dropbox_dest_folder} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━""" else: - # Fallback to keyring - auth_manager_setup.store_tokens(app_key_clean, app_secret_clean, refresh_token) - message = "✅ Dropbox connected successfully! Credentials stored securely in system keyring." + # Fallback to env_file storage + node_dir = os.path.dirname(__file__) + env_path = os.path.join(node_dir, ".env") + with open(env_path, "w") as f: + f.write(f"DROPBOX_APP_KEY={app_key_clean}\n") + f.write(f"DROPBOX_APP_SECRET={app_secret_clean}\n") + f.write(f"DROPBOX_REFRESH_TOKEN={refresh_token}\n") + f.write(f"DROPBOX_FOLDER={dropbox_dest_folder}\n") + message = "✅ Dropbox connected successfully! Credentials saved to .env file." print(f"[DropboxSetup] {message}")