removing keyring behavior
This commit is contained in:
+47
-125
@@ -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
|
||||
|
||||
+11
-17
@@ -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":
|
||||
|
||||
+15
-37
@@ -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}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user